Sync push
This commit is contained in:
@ -73,6 +73,15 @@ struct Card: Codable {
|
||||
var points: Int {
|
||||
symbol.points
|
||||
}
|
||||
|
||||
static let allCards: Set<Card> = {
|
||||
let all = Card.Suit.allCases.map { suit in
|
||||
Card.Symbol.allCases.map { symbol in
|
||||
Card(suit: suit, symbol: symbol)
|
||||
}
|
||||
}.joined()
|
||||
return Set(all)
|
||||
}()
|
||||
}
|
||||
|
||||
extension Card: CustomStringConvertible {
|
||||
@ -86,3 +95,20 @@ extension Card: Hashable {
|
||||
|
||||
}
|
||||
|
||||
struct PlayableCard {
|
||||
|
||||
let card: Card
|
||||
|
||||
let isPlayable: Bool
|
||||
|
||||
var cardInfo: CardInfo {
|
||||
.init(card: card.id, playable: isPlayable)
|
||||
}
|
||||
}
|
||||
|
||||
struct CardInfo: Codable, Equatable {
|
||||
|
||||
let card: CardId
|
||||
|
||||
let playable: Bool
|
||||
}
|
||||
|
49
Sources/App/Model/CardOrders/CardOrder.swift
Normal file
49
Sources/App/Model/CardOrders/CardOrder.swift
Normal file
@ -0,0 +1,49 @@
|
||||
//
|
||||
// File.swift
|
||||
//
|
||||
//
|
||||
// Created by iMac on 03.12.21.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum CardOrderType {
|
||||
|
||||
/// The sorting for most games, where heart is trump
|
||||
case normal
|
||||
|
||||
case wenz
|
||||
|
||||
case geier
|
||||
|
||||
case soloEichel
|
||||
|
||||
case soloBlatt
|
||||
|
||||
case soloSchelln
|
||||
}
|
||||
|
||||
protocol CardOrder {
|
||||
|
||||
static var trumpOrder: [Card] { get }
|
||||
|
||||
static var sortIndex: [Card : Int] { get }
|
||||
}
|
||||
|
||||
extension CardOrder {
|
||||
|
||||
static func consecutiveTrumps(_ cards: [Card]) -> Int {
|
||||
var count = 0
|
||||
while cards.contains(trumpOrder[count]) {
|
||||
count += 1
|
||||
}
|
||||
guard count >= 3 else {
|
||||
return 0
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
static func sort(_ cards: [Card]) -> [Card] {
|
||||
cards.sorted { sortIndex[$0]! < sortIndex[$1]! }
|
||||
}
|
||||
}
|
58
Sources/App/Model/CardOrders/NormalCardOrder.swift
Normal file
58
Sources/App/Model/CardOrders/NormalCardOrder.swift
Normal file
@ -0,0 +1,58 @@
|
||||
//
|
||||
// File.swift
|
||||
//
|
||||
//
|
||||
// Created by iMac on 03.12.21.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct NormalCardOrder: CardOrder {
|
||||
|
||||
private static let cardOrder = [
|
||||
Card(.eichel, .ober),
|
||||
Card(.blatt, .ober),
|
||||
Card(.herz, .ober),
|
||||
Card(.schelln, .ober),
|
||||
Card(.eichel, .unter),
|
||||
Card(.blatt, .unter),
|
||||
Card(.herz, .unter),
|
||||
Card(.schelln, .unter),
|
||||
Card(.herz, .ass),
|
||||
Card(.herz, .zehn),
|
||||
Card(.herz, .könig),
|
||||
Card(.herz, .neun),
|
||||
Card(.herz, .acht),
|
||||
Card(.herz, .sieben),
|
||||
Card(.eichel, .ass),
|
||||
Card(.eichel, .zehn),
|
||||
Card(.eichel, .könig),
|
||||
Card(.eichel, .neun),
|
||||
Card(.eichel, .acht),
|
||||
Card(.eichel, .sieben),
|
||||
Card(.blatt, .ass),
|
||||
Card(.blatt, .zehn),
|
||||
Card(.blatt, .könig),
|
||||
Card(.blatt, .neun),
|
||||
Card(.blatt, .acht),
|
||||
Card(.blatt, .sieben),
|
||||
Card(.schelln, .ass),
|
||||
Card(.schelln, .zehn),
|
||||
Card(.schelln, .könig),
|
||||
Card(.schelln, .neun),
|
||||
Card(.schelln, .acht),
|
||||
Card(.schelln, .sieben),
|
||||
]
|
||||
|
||||
private static let sortIndex: [Card : Int] = {
|
||||
cardOrder.enumerated().reduce(into: [:]) { $0[$1.element] = $1.offset }
|
||||
}()
|
||||
|
||||
static let trumpOrder: [Card] = Array(cardOrder[0..<8])
|
||||
|
||||
private static let trumps: Set<Card> = Set(trumpOrder)
|
||||
|
||||
static func trumpCount(_ cards: [Card]) -> Int {
|
||||
cards.filter { trumps.contains(card) }.count
|
||||
}
|
||||
}
|
312
Sources/App/Model/Dealer.swift
Normal file
312
Sources/App/Model/Dealer.swift
Normal file
@ -0,0 +1,312 @@
|
||||
import Foundation
|
||||
|
||||
private let wenzCardOder = [
|
||||
Card(.eichel, .unter),
|
||||
Card(.blatt, .unter),
|
||||
Card(.herz, .unter),
|
||||
Card(.schelln, .unter),
|
||||
Card(.herz, .ass),
|
||||
Card(.herz, .zehn),
|
||||
Card(.herz, .könig),
|
||||
Card(.herz, .ober),
|
||||
Card(.herz, .neun),
|
||||
Card(.herz, .acht),
|
||||
Card(.herz, .sieben),
|
||||
Card(.eichel, .ass),
|
||||
Card(.eichel, .zehn),
|
||||
Card(.eichel, .könig),
|
||||
Card(.eichel, .ober),
|
||||
Card(.eichel, .neun),
|
||||
Card(.eichel, .acht),
|
||||
Card(.eichel, .sieben),
|
||||
Card(.blatt, .ass),
|
||||
Card(.blatt, .zehn),
|
||||
Card(.blatt, .könig),
|
||||
Card(.blatt, .ober),
|
||||
Card(.blatt, .neun),
|
||||
Card(.blatt, .acht),
|
||||
Card(.blatt, .sieben),
|
||||
Card(.schelln, .ass),
|
||||
Card(.schelln, .zehn),
|
||||
Card(.schelln, .könig),
|
||||
Card(.schelln, .ober),
|
||||
Card(.schelln, .neun),
|
||||
Card(.schelln, .acht),
|
||||
Card(.schelln, .sieben),
|
||||
]
|
||||
|
||||
private let geierCardOrder = [
|
||||
Card(.eichel, .ober),
|
||||
Card(.blatt, .ober),
|
||||
Card(.herz, .ober),
|
||||
Card(.schelln, .ober),
|
||||
Card(.herz, .ass),
|
||||
Card(.herz, .zehn),
|
||||
Card(.herz, .könig),
|
||||
Card(.herz, .unter),
|
||||
Card(.herz, .neun),
|
||||
Card(.herz, .acht),
|
||||
Card(.herz, .sieben),
|
||||
Card(.eichel, .ass),
|
||||
Card(.eichel, .zehn),
|
||||
Card(.eichel, .könig),
|
||||
Card(.eichel, .unter),
|
||||
Card(.eichel, .neun),
|
||||
Card(.eichel, .acht),
|
||||
Card(.eichel, .sieben),
|
||||
Card(.blatt, .ass),
|
||||
Card(.blatt, .zehn),
|
||||
Card(.blatt, .könig),
|
||||
Card(.blatt, .unter),
|
||||
Card(.blatt, .neun),
|
||||
Card(.blatt, .acht),
|
||||
Card(.blatt, .sieben),
|
||||
Card(.schelln, .ass),
|
||||
Card(.schelln, .zehn),
|
||||
Card(.schelln, .könig),
|
||||
Card(.schelln, .unter),
|
||||
Card(.schelln, .neun),
|
||||
Card(.schelln, .acht),
|
||||
Card(.schelln, .sieben),
|
||||
]
|
||||
|
||||
private let eichelCardOrder = [
|
||||
Card(.eichel, .ober),
|
||||
Card(.blatt, .ober),
|
||||
Card(.herz, .ober),
|
||||
Card(.schelln, .ober),
|
||||
Card(.eichel, .unter),
|
||||
Card(.blatt, .unter),
|
||||
Card(.herz, .unter),
|
||||
Card(.schelln, .unter),
|
||||
Card(.eichel, .ass),
|
||||
Card(.eichel, .zehn),
|
||||
Card(.eichel, .könig),
|
||||
Card(.eichel, .neun),
|
||||
Card(.eichel, .acht),
|
||||
Card(.eichel, .sieben),
|
||||
Card(.blatt, .ass),
|
||||
Card(.blatt, .zehn),
|
||||
Card(.blatt, .könig),
|
||||
Card(.blatt, .neun),
|
||||
Card(.blatt, .acht),
|
||||
Card(.blatt, .sieben),
|
||||
Card(.herz, .ass),
|
||||
Card(.herz, .zehn),
|
||||
Card(.herz, .könig),
|
||||
Card(.herz, .neun),
|
||||
Card(.herz, .acht),
|
||||
Card(.herz, .sieben),
|
||||
Card(.schelln, .ass),
|
||||
Card(.schelln, .zehn),
|
||||
Card(.schelln, .könig),
|
||||
Card(.schelln, .neun),
|
||||
Card(.schelln, .acht),
|
||||
Card(.schelln, .sieben),
|
||||
]
|
||||
|
||||
private let blattCardOrder = [
|
||||
Card(.eichel, .ober),
|
||||
Card(.blatt, .ober),
|
||||
Card(.herz, .ober),
|
||||
Card(.schelln, .ober),
|
||||
Card(.eichel, .unter),
|
||||
Card(.blatt, .unter),
|
||||
Card(.herz, .unter),
|
||||
Card(.schelln, .unter),
|
||||
Card(.blatt, .ass),
|
||||
Card(.blatt, .zehn),
|
||||
Card(.blatt, .könig),
|
||||
Card(.blatt, .neun),
|
||||
Card(.blatt, .acht),
|
||||
Card(.blatt, .sieben),
|
||||
Card(.eichel, .ass),
|
||||
Card(.eichel, .zehn),
|
||||
Card(.eichel, .könig),
|
||||
Card(.eichel, .neun),
|
||||
Card(.eichel, .acht),
|
||||
Card(.eichel, .sieben),
|
||||
Card(.herz, .ass),
|
||||
Card(.herz, .zehn),
|
||||
Card(.herz, .könig),
|
||||
Card(.herz, .neun),
|
||||
Card(.herz, .acht),
|
||||
Card(.herz, .sieben),
|
||||
Card(.schelln, .ass),
|
||||
Card(.schelln, .zehn),
|
||||
Card(.schelln, .könig),
|
||||
Card(.schelln, .neun),
|
||||
Card(.schelln, .acht),
|
||||
Card(.schelln, .sieben),
|
||||
]
|
||||
|
||||
private let schellnCardOrder = [
|
||||
Card(.eichel, .ober),
|
||||
Card(.blatt, .ober),
|
||||
Card(.herz, .ober),
|
||||
Card(.schelln, .ober),
|
||||
Card(.eichel, .unter),
|
||||
Card(.blatt, .unter),
|
||||
Card(.herz, .unter),
|
||||
Card(.schelln, .unter),
|
||||
Card(.schelln, .ass),
|
||||
Card(.schelln, .zehn),
|
||||
Card(.schelln, .könig),
|
||||
Card(.schelln, .neun),
|
||||
Card(.schelln, .acht),
|
||||
Card(.schelln, .sieben),
|
||||
Card(.eichel, .ass),
|
||||
Card(.eichel, .zehn),
|
||||
Card(.eichel, .könig),
|
||||
Card(.eichel, .neun),
|
||||
Card(.eichel, .acht),
|
||||
Card(.eichel, .sieben),
|
||||
Card(.blatt, .ass),
|
||||
Card(.blatt, .zehn),
|
||||
Card(.blatt, .könig),
|
||||
Card(.blatt, .neun),
|
||||
Card(.blatt, .acht),
|
||||
Card(.blatt, .sieben),
|
||||
Card(.herz, .ass),
|
||||
Card(.herz, .zehn),
|
||||
Card(.herz, .könig),
|
||||
Card(.herz, .neun),
|
||||
Card(.herz, .acht),
|
||||
Card(.herz, .sieben),
|
||||
]
|
||||
|
||||
private let wenzSortIndex: [Card : Int] = {
|
||||
wenzCardOder.enumerated().reduce(into: [:]) { $0[$1.element] = $1.offset }
|
||||
}()
|
||||
|
||||
private let geierSortIndex: [Card : Int] = {
|
||||
geierCardOrder.enumerated().reduce(into: [:]) { $0[$1.element] = $1.offset }
|
||||
}()
|
||||
|
||||
private let eichelSortIndex: [Card : Int] = {
|
||||
eichelCardOrder.enumerated().reduce(into: [:]) { $0[$1.element] = $1.offset }
|
||||
}()
|
||||
|
||||
private let blattSortIndex: [Card : Int] = {
|
||||
blattCardOrder.enumerated().reduce(into: [:]) { $0[$1.element] = $1.offset }
|
||||
}()
|
||||
|
||||
private let schellnSortIndex: [Card : Int] = {
|
||||
schellnCardOrder.enumerated().reduce(into: [:]) { $0[$1.element] = $1.offset }
|
||||
}()
|
||||
|
||||
private let wenzTrumps: [Card] = wenzCardOder[0..<4]
|
||||
|
||||
private let geierTrumps: [Card] = geierCardOrder[0..<4]
|
||||
|
||||
private let eichelTrumps: [Card] = eichelCardOrder[0..<8]
|
||||
|
||||
private let blattTrumps: [Card] = blattCardOrder[0..<8]
|
||||
|
||||
private let schellnTrumps: [Card] = schellnCardOrder[0..<8]
|
||||
|
||||
extension Card {
|
||||
|
||||
func isTrump(in game: GameType) -> Bool {
|
||||
switch game.sortingType {
|
||||
case .normal:
|
||||
trumpsInOrder = normalCardOrder[0..<8]
|
||||
case .wenz:
|
||||
trumpsInOrder = wenzCardOder[0..<4]
|
||||
case .geier:
|
||||
trumpsInOrder = geierCardOrder[0..<4]
|
||||
case .soloEichel:
|
||||
trumpsInOrder = eichelCardOrder[0..<8]
|
||||
case .soloBlatt:
|
||||
trumpsInOrder = blattCardOrder[0..<8]
|
||||
case .soloSchelln:
|
||||
trumpsInOrder = schellnCardOrder[0..<8]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Array where Element == Card {
|
||||
|
||||
func sorted(cardOrder order: CardOrder) -> [Card] {
|
||||
switch order {
|
||||
case .normal:
|
||||
return sorted { normalSortIndex[$0]! < normalSortIndex[$1]! }
|
||||
case .wenz:
|
||||
return sorted { wenzSortIndex[$0]! < wenzSortIndex[$1]! }
|
||||
case .geier:
|
||||
return sorted { geierSortIndex[$0]! < geierSortIndex[$1]! }
|
||||
case .soloEichel:
|
||||
return sorted { eichelSortIndex[$0]! < eichelSortIndex[$1]! }
|
||||
case .soloBlatt:
|
||||
return sorted { blattSortIndex[$0]! < blattSortIndex[$1]! }
|
||||
case .soloSchelln:
|
||||
return sorted { schellnSortIndex[$0]! < schellnSortIndex[$1]! }
|
||||
}
|
||||
}
|
||||
|
||||
func consecutiveTrumps(for game: GameType) -> Int {
|
||||
var count = 0
|
||||
let trumpsInOrder: Array<Card>.SubSequence
|
||||
switch game.sortingType {
|
||||
case .normal:
|
||||
trumpsInOrder = normalCardOrder[0..<8]
|
||||
case .wenz:
|
||||
trumpsInOrder = wenzCardOder[0..<4]
|
||||
case .geier:
|
||||
trumpsInOrder = geierCardOrder[0..<4]
|
||||
case .soloEichel:
|
||||
trumpsInOrder = eichelCardOrder[0..<8]
|
||||
case .soloBlatt:
|
||||
trumpsInOrder = blattCardOrder[0..<8]
|
||||
case .soloSchelln:
|
||||
trumpsInOrder = schellnCardOrder[0..<8]
|
||||
}
|
||||
while contains(trumpsInOrder[count]) {
|
||||
count += 1
|
||||
}
|
||||
guard count >= 3 else {
|
||||
return 0
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
func trumpCount(for game: GameType) -> Int {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
Split cards into chunks to assign them to players.
|
||||
- Note: The array must contain a multiple of the `size` parameter
|
||||
*/
|
||||
func split(intoChunksOf size: Int) -> [Hand] {
|
||||
stride(from: 0, to: count, by: size).map { i in
|
||||
Array(self[i..<i+4])
|
||||
}
|
||||
}
|
||||
|
||||
var canOfferWedding: Bool {
|
||||
NormalCardOrder.trumpCount(self) == 1
|
||||
}
|
||||
}
|
||||
|
||||
struct Dealer {
|
||||
|
||||
/**
|
||||
Creates a random assignment of 4 cards per 4 players for the initial round of doubling.
|
||||
*/
|
||||
static func dealFirstCards() -> [Hand] {
|
||||
// Select 16 random cards for the first hands
|
||||
Array(Card.allCards.shuffled()[0..<16])
|
||||
.split(intoChunksOf: 4)
|
||||
.map { $0.sorted(cardOrder: .normal) }
|
||||
}
|
||||
|
||||
|
||||
static func dealRemainingCards(of initial: [Hand]) -> [Hand] {
|
||||
Card.allCards
|
||||
.subtracting(initial.reduce([], +))
|
||||
.shuffled()
|
||||
.split(intoChunksOf: 4)
|
||||
}
|
||||
|
||||
}
|
@ -32,9 +32,8 @@ struct Game: Codable {
|
||||
self.numberOfDoubles = doubles
|
||||
self.cards = cards
|
||||
self.leaders = leaders
|
||||
self.consecutiveTrumps = Dealer.consecutiveTrumps(
|
||||
in: leaders.map { cards[$0] }.joined(),
|
||||
for: type)
|
||||
self.consecutiveTrumps = Array(leaders.map { cards[$0] }.joined())
|
||||
.consecutiveTrumps(for: type)
|
||||
self.currentActor = starter
|
||||
self.lastTrickWinner = starter
|
||||
self.completedTricks = []
|
||||
|
78
Sources/App/Model/GameType.swift
Normal file
78
Sources/App/Model/GameType.swift
Normal file
@ -0,0 +1,78 @@
|
||||
import Foundation
|
||||
|
||||
enum GameType: Codable {
|
||||
|
||||
enum GameClass: Int {
|
||||
case ruf = 1
|
||||
case hochzeit = 2
|
||||
case bettel = 3
|
||||
case wenzGeier = 4
|
||||
case solo = 5
|
||||
|
||||
var cost: Int {
|
||||
switch self {
|
||||
case .ruf: return 5
|
||||
case .hochzeit: return 10
|
||||
case .bettel: return 15
|
||||
case .wenzGeier, .solo: return 20
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case rufEichel
|
||||
case rufBlatt
|
||||
case rufSchelln
|
||||
case hochzeit
|
||||
case bettel
|
||||
case wenz
|
||||
case geier
|
||||
case soloEichel
|
||||
case soloBlatt
|
||||
case soloHerz
|
||||
case soloSchelln
|
||||
|
||||
var gameClass: GameClass {
|
||||
switch self {
|
||||
case .rufEichel, .rufBlatt, .rufSchelln:
|
||||
return .ruf
|
||||
case .hochzeit:
|
||||
return .hochzeit
|
||||
case .bettel:
|
||||
return .bettel
|
||||
case .wenz, .geier:
|
||||
return .wenzGeier
|
||||
case .soloEichel, .soloBlatt, .soloHerz, .soloSchelln:
|
||||
return .solo
|
||||
}
|
||||
}
|
||||
|
||||
var isSingleGame: Bool {
|
||||
switch self {
|
||||
case .rufEichel, .rufBlatt, .rufSchelln, .hochzeit:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
var basicCost: Int {
|
||||
gameClass.cost
|
||||
}
|
||||
|
||||
var sortingType: CardOrderType {
|
||||
switch self {
|
||||
case .wenz:
|
||||
return .wenz
|
||||
case .geier:
|
||||
return .geier
|
||||
case .soloEichel:
|
||||
return .soloEichel
|
||||
case .soloBlatt:
|
||||
return .soloBlatt
|
||||
case .soloSchelln:
|
||||
return .soloSchelln
|
||||
default:
|
||||
return .normal
|
||||
}
|
||||
}
|
||||
}
|
195
Sources/App/Model/Player.swift
Normal file
195
Sources/App/Model/Player.swift
Normal file
@ -0,0 +1,195 @@
|
||||
import Foundation
|
||||
import WebSocketKit
|
||||
import CloudKit
|
||||
|
||||
private let encoder = JSONEncoder()
|
||||
|
||||
final class Player {
|
||||
|
||||
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
|
||||
|
||||
/// The action available to the player
|
||||
var actions: [Action] = []
|
||||
|
||||
/// 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
|
||||
|
||||
init(name: PlayerName) {
|
||||
self.name = name
|
||||
}
|
||||
|
||||
var rawCards: [Card] {
|
||||
handCards.map { $0.card }
|
||||
}
|
||||
|
||||
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 prepareForFirstGame(isFirstPlayer: Bool) {
|
||||
playsFirstCard = isFirstPlayer
|
||||
isNextActor = isFirstPlayer
|
||||
selectsGame = false // Not relevant in this phase
|
||||
startedCurrentTrick = isFirstPlayer
|
||||
actions = [.deal]
|
||||
didDoubleAfterFourCards = nil // Not relevant in this phase
|
||||
isStillBidding = false // Not relevant in this phase
|
||||
isGameLeader = false // Not relevant in this phase
|
||||
numberOfRaises = 0 // Not relevant in this phase
|
||||
handCards = []
|
||||
playedCard = nil
|
||||
wonTricks = []
|
||||
}
|
||||
|
||||
func assignFirstCards(_ cards: Hand) {
|
||||
selectsGame = false // Not relevant in this phase
|
||||
actions = [.initialDoubleCost, .noDoubleCost]
|
||||
didDoubleAfterFourCards = nil
|
||||
isStillBidding = false // Not relevant in this phase
|
||||
isGameLeader = false // Not relevant in this phase
|
||||
numberOfRaises = 0 // Not relevant in this phase
|
||||
handCards = cards.map { .init(card: $0, isPlayable: false) }
|
||||
playedCard = nil
|
||||
wonTricks = []
|
||||
}
|
||||
|
||||
func didDouble(_ double: Bool) {
|
||||
selectsGame = false // Not relevant in this phase
|
||||
actions = []
|
||||
didDoubleAfterFourCards = double
|
||||
isStillBidding = false // Not relevant in this phase
|
||||
isGameLeader = false // Not relevant in this phase
|
||||
numberOfRaises = 0 // Not relevant in this phase
|
||||
playedCard = nil
|
||||
wonTricks = []
|
||||
}
|
||||
|
||||
func assignRemainingCards(_ cards: Hand) {
|
||||
isStillBidding = true
|
||||
isGameLeader = false
|
||||
numberOfRaises = 0
|
||||
handCards = (rawCards + cards)
|
||||
.sorted(CardOrderType: .normal)
|
||||
.map { .init(card: $0, isPlayable: false) }
|
||||
playedCard = nil
|
||||
wonTricks = []
|
||||
}
|
||||
|
||||
func startAuction() {
|
||||
selectsGame = false // Not relevant in this phase
|
||||
if playsFirstCard {
|
||||
actions = [.withdrawFromAuction, .increaseOrMatchGame]
|
||||
} else {
|
||||
|
||||
}
|
||||
actions = []
|
||||
isStillBidding = true
|
||||
isGameLeader = false // Not relevant in this phase
|
||||
numberOfRaises = 0 // Not relevant in this phase
|
||||
playedCard = nil
|
||||
wonTricks = []
|
||||
}
|
||||
|
||||
func info(masked: Bool) -> PlayerInfo {
|
||||
.init(player: self, isMasked: masked)
|
||||
}
|
||||
}
|
||||
|
||||
extension Player {
|
||||
|
||||
/// 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 Player {
|
||||
|
||||
enum Action: String, Codable {
|
||||
/// The player can request cards to be dealt
|
||||
case deal = "deal"
|
||||
|
||||
/// The player doubles on the initial four cards
|
||||
case initialDoubleCost = "double"
|
||||
|
||||
/// The player does not double on the initial four cards
|
||||
case noDoubleCost = "skip"
|
||||
|
||||
/// The player offers a wedding (one trump card)
|
||||
case offerWedding = "wedding"
|
||||
|
||||
/// The player can choose to accept the wedding
|
||||
case acceptWedding = "accept"
|
||||
|
||||
/// The player matches or increases the game during auction
|
||||
case increaseOrMatchGame = "bid"
|
||||
|
||||
/// The player does not want to play
|
||||
case withdrawFromAuction = "out"
|
||||
|
||||
/// The player claims to win and doubles the game cost ("schießen")
|
||||
case doubleDuringGame = "raise"
|
||||
|
||||
/// The url path for the client to call (e.g. /player/deal)
|
||||
var path: String {
|
||||
rawValue
|
||||
}
|
||||
}
|
||||
}
|
233
Sources/App/Model/Table.swift
Normal file
233
Sources/App/Model/Table.swift
Normal file
@ -0,0 +1,233 @@
|
||||
import Foundation
|
||||
import WebSocketKit
|
||||
|
||||
private extension Int {
|
||||
|
||||
mutating func advanceInTable() {
|
||||
self = (self + 1) % maximumPlayersPerTable
|
||||
}
|
||||
}
|
||||
|
||||
final class Table {
|
||||
|
||||
let id: TableId
|
||||
|
||||
let name: TableName
|
||||
|
||||
let isPublic: Bool
|
||||
|
||||
var players: [Player] = []
|
||||
|
||||
var phase: GamePhase = .waitingForPlayers
|
||||
|
||||
var gameType: GameType? = nil
|
||||
|
||||
var minimumPlayableGame: GameType.GameClass = .ruf
|
||||
|
||||
/// Indicates if doubles are still allowed
|
||||
var canDoubleDuringGame = false
|
||||
|
||||
/// 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 }
|
||||
}
|
||||
|
||||
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 = Player(name: player)
|
||||
players.append(player)
|
||||
if isFull {
|
||||
prepareTableForFirstGame()
|
||||
}
|
||||
sendUpdateToAllPlayers()
|
||||
return true
|
||||
}
|
||||
|
||||
func contains(player: PlayerName) -> Bool {
|
||||
players.contains { $0.name == player }
|
||||
}
|
||||
|
||||
func select(player: PlayerName) -> Player? {
|
||||
players.first { $0.name == player }
|
||||
}
|
||||
|
||||
func player(leftOf index: Int) -> Player? {
|
||||
player(at: (index + 1) % 4)
|
||||
}
|
||||
|
||||
func player(acrossOf index: Int) -> Player? {
|
||||
player(at: (index + 2) % 4)
|
||||
}
|
||||
|
||||
func player(rightOf index: Int) -> Player? {
|
||||
player(at: (index + 3) % 4)
|
||||
}
|
||||
|
||||
func player(at index: Int) -> Player? {
|
||||
guard index < players.count else {
|
||||
return nil
|
||||
}
|
||||
return players[index]
|
||||
}
|
||||
|
||||
func remove(player: PlayerName) {
|
||||
guard contains(player: player) else {
|
||||
return
|
||||
}
|
||||
players = players.filter { $0.name != player }
|
||||
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() {
|
||||
self.phase = .waitingForPlayers
|
||||
self.gameType = nil
|
||||
self.minimumPlayableGame = .ruf // Not relevant in this phase
|
||||
self.canDoubleDuringGame = true // Not relevant in this phase
|
||||
self.didDoubleInCurrentRound = false // Not relevant in this phase
|
||||
let index = players.firstIndex { $0.playsFirstCard } ?? 0
|
||||
for i in 0..<maximumPlayersPerTable {
|
||||
players[i].prepareForFirstGame(isFirstPlayer: i == index)
|
||||
}
|
||||
}
|
||||
|
||||
private func sendUpdateToAllPlayers() {
|
||||
players.enumerated().forEach { playerIndex, player in
|
||||
guard player.isConnected else {
|
||||
return
|
||||
}
|
||||
let info = TableInfo(self, forPlayerAt: playerIndex)
|
||||
player.send(info)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Player actions
|
||||
|
||||
func perform(action: Player.Action, forPlayer player: PlayerName) -> PlayerActionResult {
|
||||
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:
|
||||
fatalError()
|
||||
case .acceptWedding:
|
||||
fatalError()
|
||||
case .increaseOrMatchGame:
|
||||
fatalError()
|
||||
case .withdrawFromAuction:
|
||||
fatalError()
|
||||
case .doubleDuringGame:
|
||||
fatalError()
|
||||
}
|
||||
}
|
||||
|
||||
private func dealInitialCards() -> PlayerActionResult {
|
||||
guard isFull else {
|
||||
return .tableNotFull
|
||||
}
|
||||
guard phase == .waitingForPlayers else {
|
||||
return .tableStateInvalid
|
||||
}
|
||||
|
||||
phase = .collectingDoubles
|
||||
gameType = nil
|
||||
minimumPlayableGame = .ruf
|
||||
|
||||
let cards = Dealer.dealFirstCards()
|
||||
for (index, player) in players.enumerated() {
|
||||
player.assignFirstCards(cards[index])
|
||||
}
|
||||
return .success
|
||||
}
|
||||
|
||||
func perform(double: Bool, forPlayer name: PlayerName) -> PlayerActionResult {
|
||||
let player = select(player: player)!
|
||||
player.didDouble(double)
|
||||
if allPlayersFinishedDoubling {
|
||||
dealAdditionalCards()
|
||||
}
|
||||
return .success
|
||||
}
|
||||
|
||||
private func dealAdditionalCards() {
|
||||
let cards = Dealer.dealRemainingCards(of: players.map { $0.rawCards })
|
||||
for (index, player) in players.enumerated() {
|
||||
player.assignRemainingCards(cards[index])
|
||||
}
|
||||
return .success
|
||||
}
|
||||
|
||||
private func startAuction() {
|
||||
players.forEach { $0.startAuction() }
|
||||
minimumPlayableGame = .ruf
|
||||
}
|
||||
|
||||
private func reset() {
|
||||
phase = .waitingForPlayers
|
||||
gameType = nil
|
||||
minimumPlayableGame = .ruf
|
||||
}
|
||||
}
|
||||
|
||||
extension Table {
|
||||
|
||||
var isFull: Bool {
|
||||
players.count == maximumPlayersPerTable
|
||||
}
|
||||
|
||||
var publicInfo: PublicTableInfo {
|
||||
.init(id: id, name: name, players: playerNames)
|
||||
}
|
||||
|
||||
var playerNames: [PlayerName] {
|
||||
players.map { $0.name }
|
||||
}
|
||||
|
||||
func compileInfo(for player: PlayerName) -> TableInfo? {
|
||||
guard let index = players.firstIndex(where: { $0.name == player }) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return TableInfo(self, forPlayerAt: index)
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@ typealias Trick = [Card]
|
||||
extension Trick {
|
||||
|
||||
func winnerIndex(forGameType type: GameType) -> Int {
|
||||
let highCard = Dealer.sort(cards: self, using: type.sortingType).first!
|
||||
let highCard = sorted(cardOrder: type.sortingType).first!
|
||||
return firstIndex(of: highCard)!
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user