Remove old files
This commit is contained in:
parent
afadc56c40
commit
cfba0fe467
@ -1,479 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,577 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
import WebSocketKit
|
|
||||||
|
|
||||||
private extension Int {
|
|
||||||
|
|
||||||
mutating func advanceInTable() {
|
|
||||||
self = (self + 1) % maximumPlayersPerTable
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final class OldTable {
|
|
||||||
|
|
||||||
let id: TableId
|
|
||||||
|
|
||||||
let name: TableName
|
|
||||||
|
|
||||||
let isPublic: Bool
|
|
||||||
|
|
||||||
var players: [OldPlayer] = []
|
|
||||||
|
|
||||||
var phase: GamePhase = .waitingForPlayers
|
|
||||||
|
|
||||||
var gameType: GameType? = nil
|
|
||||||
|
|
||||||
var minimumPlayableGame: GameType.GameClass = .none
|
|
||||||
|
|
||||||
/// Indicates if any player doubled during the current round, extending it to the next round
|
|
||||||
var didDoubleInCurrentRound = false
|
|
||||||
|
|
||||||
/// Indicates that all players acted after the first four cards
|
|
||||||
var allPlayersFinishedDoubling: Bool {
|
|
||||||
!players.contains { $0.didDoubleAfterFourCards == nil }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// At least one double exists after all players acted on their first cards
|
|
||||||
var initialDoubleExists: Bool {
|
|
||||||
players.contains { $0.didDoubleAfterFourCards == true }
|
|
||||||
}
|
|
||||||
|
|
||||||
var weddingOfferExists: Bool {
|
|
||||||
players.contains { $0.offersWedding }
|
|
||||||
}
|
|
||||||
|
|
||||||
var weddingAcceptExists: Bool {
|
|
||||||
players.contains { $0.wouldAcceptWedding }
|
|
||||||
}
|
|
||||||
|
|
||||||
var hasAuctionWinner: Bool {
|
|
||||||
numberOfRemainingBidders == 1
|
|
||||||
}
|
|
||||||
|
|
||||||
var numberOfRemainingBidders: Int {
|
|
||||||
players.filter { $0.isStillBidding }.count
|
|
||||||
}
|
|
||||||
|
|
||||||
var auctionWinner: OldPlayer {
|
|
||||||
players.first { $0.isStillBidding }!
|
|
||||||
}
|
|
||||||
|
|
||||||
var hasCompletedTrick: Bool {
|
|
||||||
!players.contains { $0.playedCard == nil }
|
|
||||||
}
|
|
||||||
|
|
||||||
var completedTrick: Trick? {
|
|
||||||
let trick = players.compactMap { $0.playedCard }
|
|
||||||
guard trick.count == maximumPlayersPerTable else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return trick
|
|
||||||
}
|
|
||||||
|
|
||||||
var currentTrick: [Card] {
|
|
||||||
players.compactMap { $0.playedCard }
|
|
||||||
}
|
|
||||||
|
|
||||||
var didFinishGame: Bool {
|
|
||||||
!players.contains { !$0.handCards.isEmpty }
|
|
||||||
}
|
|
||||||
|
|
||||||
init(id: TableId, name: TableName, isPublic: Bool) {
|
|
||||||
self.id = id
|
|
||||||
self.name = name
|
|
||||||
self.isPublic = isPublic
|
|
||||||
}
|
|
||||||
|
|
||||||
init(newTable name: TableName, isPublic: Bool) {
|
|
||||||
self.id = .newToken()
|
|
||||||
self.name = name
|
|
||||||
self.isPublic = isPublic
|
|
||||||
}
|
|
||||||
|
|
||||||
func add(player: PlayerName) -> Bool {
|
|
||||||
guard !isFull else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
let player = OldPlayer(name: player)
|
|
||||||
players.append(player)
|
|
||||||
if isFull {
|
|
||||||
prepareTableForFirstGame()
|
|
||||||
}
|
|
||||||
sendUpdateToAllPlayers()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func contains(player: PlayerName) -> Bool {
|
|
||||||
players.contains { $0.name == player }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The player to play the first card of the current game
|
|
||||||
var firstPlayer: OldPlayer {
|
|
||||||
players.first { $0.playsFirstCard }!
|
|
||||||
}
|
|
||||||
|
|
||||||
func select(player: PlayerName) -> OldPlayer? {
|
|
||||||
players.first { $0.name == player }
|
|
||||||
}
|
|
||||||
|
|
||||||
var indexOfTrickStarter: Int {
|
|
||||||
players.firstIndex { $0.startedCurrentTrick }!
|
|
||||||
}
|
|
||||||
|
|
||||||
func player(at index: Int) -> OldPlayer? {
|
|
||||||
guard index < players.count else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return players[index]
|
|
||||||
}
|
|
||||||
|
|
||||||
func index(of player: OldPlayer) -> Int {
|
|
||||||
players.firstIndex(of: player)!
|
|
||||||
}
|
|
||||||
|
|
||||||
func nextPlayer(after player: OldPlayer) -> OldPlayer {
|
|
||||||
let i = index(of: player)
|
|
||||||
let newIndex = (i + 1) % maximumPlayersPerTable
|
|
||||||
return players[newIndex]
|
|
||||||
}
|
|
||||||
|
|
||||||
func nextBidder(after player: OldPlayer) -> OldPlayer {
|
|
||||||
// Find next player to place bid
|
|
||||||
let index = index(of: player)
|
|
||||||
for i in 1..<4 {
|
|
||||||
let player = players[(index + i) % 4]
|
|
||||||
guard player.isStillBidding, !player.offersWedding else {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return player
|
|
||||||
}
|
|
||||||
return player
|
|
||||||
}
|
|
||||||
|
|
||||||
func availableGames(forPlayerAt index: Int) -> [GameType] {
|
|
||||||
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
func remove(player: PlayerName) {
|
|
||||||
guard let index = players.firstIndex(where: { $0.name == player }) else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let removedPlayer = players[index]
|
|
||||||
if removedPlayer.playsFirstCard {
|
|
||||||
players[(index + 1) % players.count].playsFirstCard = true
|
|
||||||
}
|
|
||||||
players.remove(at: index)
|
|
||||||
reset()
|
|
||||||
}
|
|
||||||
|
|
||||||
func connect(player name: PlayerName, using socket: WebSocket) -> Bool {
|
|
||||||
guard let player = select(player: name) else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
player.connect(using: socket)
|
|
||||||
sendUpdateToAllPlayers()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func disconnect(player name: PlayerName) {
|
|
||||||
guard let player = select(player: name) else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
guard player.disconnect() else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
sendUpdateToAllPlayers()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
private func prepareTableForFirstGame() {
|
|
||||||
phase = .waitingForPlayers
|
|
||||||
gameType = nil
|
|
||||||
minimumPlayableGame = .none // Not relevant in this phase
|
|
||||||
didDoubleInCurrentRound = false // Not relevant in this phase
|
|
||||||
let index = players.firstIndex { $0.playsFirstCard } ?? 0
|
|
||||||
for i in 0..<maximumPlayersPerTable {
|
|
||||||
players[i].prepareForNewGame(isFirstPlayer: i == index)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func sendUpdateToAllPlayers() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Player actions
|
|
||||||
|
|
||||||
func play(card: Card, player name: PlayerName) -> PlayCardResult {
|
|
||||||
let player = select(player: name)!
|
|
||||||
if phase == .selectWeddingCard {
|
|
||||||
return selectedCardForWedding(card: card, player: player)
|
|
||||||
}
|
|
||||||
guard let game = gameType, player.hasPlayable(card: card) else {
|
|
||||||
// Player only has playable cards when it is active
|
|
||||||
return .invalidCard
|
|
||||||
}
|
|
||||||
if hasCompletedTrick {
|
|
||||||
// Hide cards from last trick when next card is played
|
|
||||||
players.forEach { $0.clearLastTrick() }
|
|
||||||
}
|
|
||||||
player.play(card: card)
|
|
||||||
if let completedTrick = completedTrick {
|
|
||||||
didFinish(trick: completedTrick, in: game)
|
|
||||||
// Update cards for empty trick
|
|
||||||
players.forEach { $0.setPlayableCards(forCurrentTrick: [], in: game) }
|
|
||||||
} else {
|
|
||||||
let next = nextPlayer(after: player)
|
|
||||||
next.isNextActor = true
|
|
||||||
player.isNextActor = false
|
|
||||||
// Update cards for empty trick
|
|
||||||
players.forEach { $0.setPlayableCards(forCurrentTrick: currentTrick, in: game) }
|
|
||||||
}
|
|
||||||
if didFinishGame {
|
|
||||||
finishedGame()
|
|
||||||
}
|
|
||||||
sendUpdateToAllPlayers()
|
|
||||||
return .success
|
|
||||||
}
|
|
||||||
|
|
||||||
private func finishedGame() {
|
|
||||||
phase = .gameFinished
|
|
||||||
players.forEach { $0.didFinishGame() }
|
|
||||||
guard didFinishGame else {
|
|
||||||
// Either no doubles or bids
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// TODO: Calculate winner, points, cost
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func didFinish(trick: Trick, in game: GameType) {
|
|
||||||
// If trick is completed, calculate winner
|
|
||||||
let startIndex = indexOfTrickStarter
|
|
||||||
let rotated = trick.rotated(toStartAt: startIndex)
|
|
||||||
let index = rotated.highCardIndex(forGame: game)
|
|
||||||
print("Winner \(index) for \(rotated)")
|
|
||||||
let winner = players[(startIndex + index) % 4]
|
|
||||||
players.forEach {
|
|
||||||
$0.didFinish(trick: trick,
|
|
||||||
winner: winner == $0,
|
|
||||||
canDoubleInNextRound: didDoubleInCurrentRound)
|
|
||||||
}
|
|
||||||
if game == .bettel && winner.isGameLeader {
|
|
||||||
// A bettel is lost if a single trick is won by the leader
|
|
||||||
finishedGame()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
didDoubleInCurrentRound = false
|
|
||||||
}
|
|
||||||
|
|
||||||
func perform(action: PlayerAction, forPlayer player: PlayerName) -> PlayerActionResult {
|
|
||||||
let player = select(player: player)!
|
|
||||||
guard player.canPerform(action) else {
|
|
||||||
print("Player \(player) wants to \(action.id), but only allowed: \(player.actions)")
|
|
||||||
return .tableStateInvalid
|
|
||||||
}
|
|
||||||
defer { sendUpdateToAllPlayers() }
|
|
||||||
switch action {
|
|
||||||
case .deal:
|
|
||||||
return dealInitialCards()
|
|
||||||
case .initialDoubleCost:
|
|
||||||
return perform(double: true, forPlayer: player)
|
|
||||||
case .noDoubleCost:
|
|
||||||
return perform(double: false, forPlayer: player)
|
|
||||||
case .offerWedding:
|
|
||||||
return performWeddingCall(forPlayer: player)
|
|
||||||
case .acceptWedding:
|
|
||||||
return handleWeddingAccept(forPlayer: player)
|
|
||||||
case .increaseOrMatchGame:
|
|
||||||
return performBidIncrease(forPlayer: player)
|
|
||||||
case .withdrawFromAuction:
|
|
||||||
return performWithdrawl(forPlayer: player)
|
|
||||||
case .doubleDuringGame:
|
|
||||||
return performDoubleDuringGame(forPlayer: player)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func dealInitialCards() -> PlayerActionResult {
|
|
||||||
guard isFull else {
|
|
||||||
return .tableNotFull
|
|
||||||
}
|
|
||||||
guard phase == .waitingForPlayers || phase == .gameFinished else {
|
|
||||||
return .tableStateInvalid
|
|
||||||
}
|
|
||||||
if phase == .gameFinished {
|
|
||||||
prepareForNextGame()
|
|
||||||
}
|
|
||||||
|
|
||||||
let cards = Dealer.dealFirstCards()
|
|
||||||
for (index, player) in players.enumerated() {
|
|
||||||
player.assignFirstCards(cards[index])
|
|
||||||
}
|
|
||||||
phase = .collectingDoubles
|
|
||||||
gameType = nil
|
|
||||||
return .success
|
|
||||||
}
|
|
||||||
|
|
||||||
func perform(double: Bool, forPlayer player: OldPlayer) -> PlayerActionResult {
|
|
||||||
player.didDouble(double)
|
|
||||||
guard allPlayersFinishedDoubling else {
|
|
||||||
return .success
|
|
||||||
}
|
|
||||||
if initialDoubleExists {
|
|
||||||
dealAdditionalCards()
|
|
||||||
} else {
|
|
||||||
finishedGame()
|
|
||||||
}
|
|
||||||
return .success
|
|
||||||
}
|
|
||||||
|
|
||||||
private func dealAdditionalCards() {
|
|
||||||
let cards = Dealer.dealRemainingCards(of: players.map { $0.rawCards })
|
|
||||||
for (index, player) in players.enumerated() {
|
|
||||||
player.assignRemainingCards(cards[index])
|
|
||||||
}
|
|
||||||
players.forEach { $0.startAuction() }
|
|
||||||
minimumPlayableGame = .none
|
|
||||||
phase = .bidding
|
|
||||||
}
|
|
||||||
|
|
||||||
private func performWeddingCall(forPlayer player: OldPlayer) -> PlayerActionResult {
|
|
||||||
guard phase == .bidding else {
|
|
||||||
print("Invalid phase \(phase) for wedding call")
|
|
||||||
return .tableStateInvalid
|
|
||||||
}
|
|
||||||
guard minimumPlayableGame.allowsWedding else {
|
|
||||||
print("Invalid minimum game \(minimumPlayableGame) for wedding call")
|
|
||||||
return .tableStateInvalid
|
|
||||||
}
|
|
||||||
guard player.canOfferWedding else {
|
|
||||||
print("Player does not offer wedding")
|
|
||||||
return .tableStateInvalid
|
|
||||||
}
|
|
||||||
guard !weddingOfferExists else {
|
|
||||||
// Only one wedding allowed at the table
|
|
||||||
print("Already one wedding at table")
|
|
||||||
return .tableStateInvalid
|
|
||||||
}
|
|
||||||
// Only allow wedding acceptance or outbidding
|
|
||||||
players.forEach { $0.weddingOfferExists() }
|
|
||||||
player.offerWedding()
|
|
||||||
firstPlayer.hasWeddingOffer()
|
|
||||||
minimumPlayableGame = .bettel
|
|
||||||
return .success
|
|
||||||
}
|
|
||||||
|
|
||||||
private func performBidIncrease(forPlayer player: OldPlayer) -> PlayerActionResult {
|
|
||||||
guard phase == .bidding else {
|
|
||||||
return .tableStateInvalid
|
|
||||||
}
|
|
||||||
if weddingOfferExists {
|
|
||||||
// Anyone except the offerer can outbid a wedding
|
|
||||||
return handleWeddingOutbid(forPlayer: player)
|
|
||||||
}
|
|
||||||
guard player.isNextActor else {
|
|
||||||
return .tableStateInvalid
|
|
||||||
}
|
|
||||||
// TODO: Check if new player sits before old player
|
|
||||||
// then don't increase game
|
|
||||||
minimumPlayableGame.increase()
|
|
||||||
if !minimumPlayableGame.allowsWedding {
|
|
||||||
// Remove wedding offers
|
|
||||||
players.forEach { $0.weddingOutbid() }
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Remove highest bidder from old player
|
|
||||||
player.didPerformBid()
|
|
||||||
|
|
||||||
if numberOfRemainingBidders == 1 {
|
|
||||||
selectGame(player: player)
|
|
||||||
return .success
|
|
||||||
}
|
|
||||||
// Find next player to place bid
|
|
||||||
nextBidder(after: player).requiresBid(hasWedding: false)
|
|
||||||
return .success
|
|
||||||
}
|
|
||||||
|
|
||||||
private func handleWeddingOutbid(forPlayer player: OldPlayer) -> PlayerActionResult {
|
|
||||||
if player.offersWedding {
|
|
||||||
// A player offering a wedding can't outbid itself
|
|
||||||
return .tableStateInvalid
|
|
||||||
}
|
|
||||||
players.forEach { $0.weddingOutbid() }
|
|
||||||
nextBidder(after: player).requiresBid(hasWedding: false)
|
|
||||||
return .success
|
|
||||||
}
|
|
||||||
|
|
||||||
private func handleWeddingAccept(forPlayer player: OldPlayer) -> PlayerActionResult {
|
|
||||||
guard phase == .bidding else {
|
|
||||||
return .tableStateInvalid
|
|
||||||
}
|
|
||||||
guard minimumPlayableGame.allowsWedding else {
|
|
||||||
return .tableStateInvalid
|
|
||||||
}
|
|
||||||
guard weddingOfferExists else {
|
|
||||||
return .tableStateInvalid
|
|
||||||
}
|
|
||||||
guard player.isNextActor else {
|
|
||||||
return .tableStateInvalid
|
|
||||||
}
|
|
||||||
guard !player.offersWedding else {
|
|
||||||
return .tableStateInvalid
|
|
||||||
}
|
|
||||||
guard !weddingAcceptExists else {
|
|
||||||
return .tableStateInvalid
|
|
||||||
}
|
|
||||||
if hasAuctionWinner {
|
|
||||||
selectedWedding(player: player)
|
|
||||||
return .success
|
|
||||||
}
|
|
||||||
minimumPlayableGame = .bettel
|
|
||||||
players.forEach {
|
|
||||||
$0.weddingAccepted()
|
|
||||||
}
|
|
||||||
player.acceptWedding()
|
|
||||||
nextBidder(after: player).requiresBid(hasWedding: false)
|
|
||||||
return .success
|
|
||||||
}
|
|
||||||
|
|
||||||
private func selectedWedding(player: OldPlayer) {
|
|
||||||
minimumPlayableGame = .none
|
|
||||||
gameType = .hochzeit
|
|
||||||
phase = .selectWeddingCard
|
|
||||||
players.forEach { $0.auctionEnded() }
|
|
||||||
player.mustSelectWeddingCard()
|
|
||||||
}
|
|
||||||
|
|
||||||
private func selectedCardForWedding(card: Card, player: OldPlayer) -> PlayCardResult {
|
|
||||||
guard player.isNextActor,
|
|
||||||
player.wouldAcceptWedding,
|
|
||||||
weddingOfferExists else {
|
|
||||||
return .tableStateInvalid
|
|
||||||
}
|
|
||||||
guard !card.isTrump(in: .hochzeit),
|
|
||||||
player.has(card: card) else {
|
|
||||||
return .invalidCard
|
|
||||||
}
|
|
||||||
// Swap the cards
|
|
||||||
let offerer = players.first { $0.offersWedding }!
|
|
||||||
let offeredCard = offerer.replaceWeddingCard(with: card)
|
|
||||||
player.replace(card: card, with: offeredCard)
|
|
||||||
|
|
||||||
// Start the game
|
|
||||||
gameType = .hochzeit
|
|
||||||
players.forEach { $0.start(game: .hochzeit) }
|
|
||||||
player.switchLeadership()
|
|
||||||
offerer.switchLeadership()
|
|
||||||
return .success
|
|
||||||
}
|
|
||||||
|
|
||||||
private func performWithdrawl(forPlayer player: OldPlayer) -> PlayerActionResult {
|
|
||||||
guard phase == .bidding,
|
|
||||||
player.isNextActor,
|
|
||||||
player.isStillBidding else {
|
|
||||||
return .tableStateInvalid
|
|
||||||
}
|
|
||||||
player.withdrawFromBidding()
|
|
||||||
switch numberOfRemainingBidders {
|
|
||||||
case 1:
|
|
||||||
if minimumPlayableGame != .none {
|
|
||||||
// Will only be called when at least one player placed a bid
|
|
||||||
selectGame(player: auctionWinner)
|
|
||||||
return .success
|
|
||||||
}
|
|
||||||
case 0:
|
|
||||||
// All players withdrawn, deal new cards
|
|
||||||
finishedGame()
|
|
||||||
return .success
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
nextBidder(after: player).requiresBid(hasWedding: weddingOfferExists)
|
|
||||||
return .success
|
|
||||||
}
|
|
||||||
|
|
||||||
private func prepareForNextGame() {
|
|
||||||
let first = firstPlayer
|
|
||||||
let newPlayer = self.nextBidder(after: first)
|
|
||||||
first.playsFirstCard = false
|
|
||||||
newPlayer.playsFirstCard = true
|
|
||||||
print("Made \(newPlayer.name) to new starter")
|
|
||||||
prepareTableForFirstGame()
|
|
||||||
}
|
|
||||||
|
|
||||||
private func selectGame(player: OldPlayer) {
|
|
||||||
gameType = nil
|
|
||||||
phase = .selectGame
|
|
||||||
players.forEach { $0.auctionEnded() }
|
|
||||||
player.mustSelectGame()
|
|
||||||
}
|
|
||||||
|
|
||||||
func select(game: GameType, player: PlayerName) -> PlayerActionResult {
|
|
||||||
let player = select(player: player)!
|
|
||||||
guard phase == .selectGame, player.selectsGame, game != .hochzeit else {
|
|
||||||
return .tableStateInvalid
|
|
||||||
}
|
|
||||||
guard game.gameClass >= minimumPlayableGame else {
|
|
||||||
return .tableStateInvalid
|
|
||||||
}
|
|
||||||
defer { sendUpdateToAllPlayers() }
|
|
||||||
guard let suit = game.calledSuit else {
|
|
||||||
phase = .playing
|
|
||||||
gameType = game
|
|
||||||
minimumPlayableGame = .none
|
|
||||||
players.forEach { $0.start(game: game) }
|
|
||||||
player.switchLeadership()
|
|
||||||
|
|
||||||
return .success
|
|
||||||
}
|
|
||||||
|
|
||||||
guard player.canPlay(game: game) else {
|
|
||||||
return .tableStateInvalid
|
|
||||||
}
|
|
||||||
phase = .playing
|
|
||||||
gameType = game
|
|
||||||
minimumPlayableGame = .none
|
|
||||||
players.forEach { $0.start(game: game) }
|
|
||||||
player.switchLeadership()
|
|
||||||
// Find called player
|
|
||||||
let ace = Card(suit, .ass)
|
|
||||||
players.first { $0.rawCards.contains(ace) }!.switchLeadership()
|
|
||||||
return .success
|
|
||||||
}
|
|
||||||
|
|
||||||
private func performDoubleDuringGame(forPlayer player: OldPlayer) -> PlayerActionResult {
|
|
||||||
guard phase == .playing, !player.isGameLeader else {
|
|
||||||
return .tableStateInvalid
|
|
||||||
}
|
|
||||||
player.numberOfRaises += 1
|
|
||||||
players.forEach { $0.switchLeadership() }
|
|
||||||
didDoubleInCurrentRound = true
|
|
||||||
return .success
|
|
||||||
}
|
|
||||||
|
|
||||||
private func reset() {
|
|
||||||
phase = .waitingForPlayers
|
|
||||||
gameType = nil
|
|
||||||
minimumPlayableGame = .none
|
|
||||||
for player in players {
|
|
||||||
player.prepareForNewGame(isFirstPlayer: player.playsFirstCard)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension OldTable {
|
|
||||||
|
|
||||||
var isFull: Bool {
|
|
||||||
players.count == maximumPlayersPerTable
|
|
||||||
}
|
|
||||||
|
|
||||||
var publicInfo: PublicTableInfo {
|
|
||||||
.init(id: id, name: name, players: playerNames)
|
|
||||||
}
|
|
||||||
|
|
||||||
var playerNames: [PlayerName] {
|
|
||||||
players.map { $0.name }
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,6 +1,9 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import WebSocketKit
|
import WebSocketKit
|
||||||
|
|
||||||
|
|
||||||
|
let numberOfCardsPerPlayer = 8
|
||||||
|
|
||||||
class AbstractTable<TablePlayer> where TablePlayer: Player {
|
class AbstractTable<TablePlayer> where TablePlayer: Player {
|
||||||
|
|
||||||
/// The unique id of the table
|
/// The unique id of the table
|
||||||
|
Loading…
Reference in New Issue
Block a user