Add card dealing
This commit is contained in:
parent
1ee3fbc0ab
commit
cdf079698e
@ -1,10 +1,19 @@
|
||||
import Foundation
|
||||
|
||||
struct CardInfo: Codable {
|
||||
struct CardInfo: ClientMessage {
|
||||
|
||||
static let type: ClientMessageType = .cardInfo
|
||||
|
||||
struct HandCard: Codable {
|
||||
|
||||
let card: CardId
|
||||
|
||||
let playable: Bool
|
||||
}
|
||||
|
||||
/// The cards for a player
|
||||
let cards: [CardId]
|
||||
let cards: [HandCard]
|
||||
|
||||
/// Indicates if the card can be played
|
||||
let playable: [Bool]
|
||||
// The cards on the table, as seen from the players perspective
|
||||
let tableCards: [CardId]
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import Foundation
|
||||
|
||||
enum GameType {
|
||||
enum GameType: Codable {
|
||||
|
||||
case rufEichel
|
||||
case rufBlatt
|
||||
@ -52,5 +52,37 @@ enum GameType {
|
||||
return 20
|
||||
}
|
||||
}
|
||||
|
||||
var sortingType: CardSortingStrategy {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum CardSortingStrategy {
|
||||
|
||||
/// The sorting for most games, where heart is trump
|
||||
case normal
|
||||
|
||||
case wenz
|
||||
|
||||
case geier
|
||||
|
||||
case soloEichel
|
||||
|
||||
case soloBlatt
|
||||
|
||||
case soloSchelln
|
||||
}
|
||||
|
@ -1,14 +1,28 @@
|
||||
import Foundation
|
||||
|
||||
struct TableInfo: Codable {
|
||||
struct TableInfo: ClientMessage {
|
||||
|
||||
static let type: ClientMessageType = .tableInfo
|
||||
|
||||
let id: String
|
||||
|
||||
let name: String
|
||||
|
||||
var players: [PlayerName]
|
||||
let players: [PlayerState]
|
||||
|
||||
var connected: [Bool]
|
||||
let tableIsFull: Bool
|
||||
|
||||
struct PlayerState: Codable, Equatable {
|
||||
|
||||
let name: PlayerName
|
||||
|
||||
let connected: Bool
|
||||
|
||||
init(name: PlayerName, connected: Bool) {
|
||||
self.name = name
|
||||
self.connected = connected
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension TableInfo: Comparable {
|
||||
|
71
Sources/App/Model/Game.swift
Normal file
71
Sources/App/Model/Game.swift
Normal file
@ -0,0 +1,71 @@
|
||||
import Foundation
|
||||
|
||||
typealias Hand = [Card]
|
||||
|
||||
/// The number of tricks ("Stich") per game
|
||||
let tricksPerGame = 8
|
||||
|
||||
struct Game: Codable {
|
||||
|
||||
let type: GameType
|
||||
|
||||
let numberOfDoubles: Int
|
||||
|
||||
/// The player(s) leading the game, i.e. they lose on 60-60
|
||||
let leaders: [Int]
|
||||
|
||||
/// The remaining cards for all players
|
||||
var cards: [Hand]
|
||||
|
||||
/// The total number of consecutive trump cards for one party (starts counting at 3)
|
||||
let consecutiveTrumps: Int
|
||||
|
||||
var lastTrickWinner: Int
|
||||
|
||||
var currentActor: Int
|
||||
|
||||
/// The finished tricks, with each trick sorted according to the player order of the table
|
||||
var completedTricks: [Trick]
|
||||
|
||||
init(type: GameType, doubles: Int, cards: [Hand], leaders: [Int], starter: Int) {
|
||||
self.type = type
|
||||
self.numberOfDoubles = doubles
|
||||
self.cards = cards
|
||||
self.leaders = leaders
|
||||
self.consecutiveTrumps = Dealer.consecutiveTrumps(
|
||||
in: leaders.map { cards[$0] }.joined(),
|
||||
for: type)
|
||||
self.currentActor = starter
|
||||
self.lastTrickWinner = starter
|
||||
self.completedTricks = []
|
||||
}
|
||||
|
||||
var isAtEnd: Bool {
|
||||
completedTricks.count == tricksPerGame
|
||||
}
|
||||
|
||||
var hasNotStarted: Bool {
|
||||
!cards.contains { !$0.isEmpty }
|
||||
}
|
||||
|
||||
var pointsOfLeaders: Int {
|
||||
leaders.map(pointsOfPlayer).reduce(0, +)
|
||||
}
|
||||
|
||||
func pointsOfPlayer(index: Int) -> Int {
|
||||
completedTricks
|
||||
.filter { $0.winnerIndex(forGameType: type) == index }
|
||||
.map { $0.points }
|
||||
.reduce(0, +)
|
||||
}
|
||||
|
||||
/// The cost of the game, in cents
|
||||
var cost: Int {
|
||||
// TODO: Add läufer and schwarz, schneider
|
||||
type.basicCost * costMultiplier
|
||||
}
|
||||
|
||||
var costMultiplier: Int {
|
||||
2 ^^ numberOfDoubles
|
||||
}
|
||||
}
|
22
Sources/App/Model/GamePhase.swift
Normal file
22
Sources/App/Model/GamePhase.swift
Normal file
@ -0,0 +1,22 @@
|
||||
import Foundation
|
||||
|
||||
/**
|
||||
The phase of a table
|
||||
*/
|
||||
enum GamePhase: String, Codable {
|
||||
|
||||
/// The table is not yet full, so no game can be started
|
||||
case waitingForPlayers = "waiting"
|
||||
|
||||
/// The players are specifying if they want to double ("legen")
|
||||
case collectingDoubles = "doubles"
|
||||
|
||||
/// The game negotiation is ongoing
|
||||
case bidding = "bidding"
|
||||
|
||||
/// The game is in progress
|
||||
case playing = "play"
|
||||
|
||||
/// The game is over
|
||||
case gameFinished = "done"
|
||||
}
|
16
Sources/App/Model/Trick.swift
Normal file
16
Sources/App/Model/Trick.swift
Normal file
@ -0,0 +1,16 @@
|
||||
import Foundation
|
||||
|
||||
typealias Trick = [Card]
|
||||
|
||||
extension Trick {
|
||||
|
||||
func winnerIndex(forGameType type: GameType) -> Int {
|
||||
let highCard = Dealer.sort(cards: self, using: type.sortingType).first!
|
||||
return firstIndex(of: highCard)!
|
||||
}
|
||||
|
||||
var points: Int {
|
||||
map { $0.points }
|
||||
.reduce(0, +)
|
||||
}
|
||||
}
|
15
Sources/App/Results/DealCardsResult.swift
Normal file
15
Sources/App/Results/DealCardsResult.swift
Normal file
@ -0,0 +1,15 @@
|
||||
import Foundation
|
||||
|
||||
|
||||
enum DealCardResult {
|
||||
|
||||
case success
|
||||
|
||||
case invalidToken
|
||||
|
||||
case noTableJoined
|
||||
|
||||
case tableNotFull
|
||||
|
||||
case tableStateInvalid
|
||||
}
|
298
Sources/App/Sorting/Dealer.swift
Normal file
298
Sources/App/Sorting/Dealer.swift
Normal file
@ -0,0 +1,298 @@
|
||||
import Foundation
|
||||
|
||||
struct Dealer {
|
||||
|
||||
private static let normalCardOrder = [
|
||||
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 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 static 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 static 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 static 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 static 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 static let normalSortIndex: [Card : Int] = {
|
||||
normalCardOrder.enumerated().reduce(into: [:]) { $0[$1.element] = $1.offset }
|
||||
}()
|
||||
|
||||
private static let wenzSortIndex: [Card : Int] = {
|
||||
wenzCardOder.enumerated().reduce(into: [:]) { $0[$1.element] = $1.offset }
|
||||
}()
|
||||
|
||||
private static let geierSortIndex: [Card : Int] = {
|
||||
geierCardOrder.enumerated().reduce(into: [:]) { $0[$1.element] = $1.offset }
|
||||
}()
|
||||
|
||||
private static let eichelSortIndex: [Card : Int] = {
|
||||
eichelCardOrder.enumerated().reduce(into: [:]) { $0[$1.element] = $1.offset }
|
||||
}()
|
||||
|
||||
private static let blattSortIndex: [Card : Int] = {
|
||||
blattCardOrder.enumerated().reduce(into: [:]) { $0[$1.element] = $1.offset }
|
||||
}()
|
||||
|
||||
private static let schellnSortIndex: [Card : Int] = {
|
||||
schellnCardOrder.enumerated().reduce(into: [:]) { $0[$1.element] = $1.offset }
|
||||
}()
|
||||
|
||||
static func sort<T>(cards: T, using strategy: CardSortingStrategy = .normal) -> [Card] where T: Sequence, T.Element == Card {
|
||||
switch strategy {
|
||||
case .normal:
|
||||
return cards.sorted { normalSortIndex[$0]! < normalSortIndex[$1]! }
|
||||
case .wenz:
|
||||
return cards.sorted { wenzSortIndex[$0]! < wenzSortIndex[$1]! }
|
||||
case .geier:
|
||||
return cards.sorted { geierSortIndex[$0]! < geierSortIndex[$1]! }
|
||||
case .soloEichel:
|
||||
return cards.sorted { eichelSortIndex[$0]! < eichelSortIndex[$1]! }
|
||||
case .soloBlatt:
|
||||
return cards.sorted { blattSortIndex[$0]! < blattSortIndex[$1]! }
|
||||
case .soloSchelln:
|
||||
return cards.sorted { schellnSortIndex[$0]! < schellnSortIndex[$1]! }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a random assignment of 8 cards per 4 players.
|
||||
*/
|
||||
static func deal() -> [[Card]] {
|
||||
let deck = Card.Suit.allCases.map { suit in
|
||||
Card.Symbol.allCases.map { symbol in
|
||||
Card(suit: suit, symbol: symbol)
|
||||
}
|
||||
}.joined()
|
||||
let random = Array(deck).shuffled()
|
||||
return (0..<4).map { part -> Array<Card>.SubSequence in
|
||||
let start = part * 8
|
||||
let end = start + 8
|
||||
return random[start..<end]
|
||||
}.map { sort(cards: $0) }
|
||||
}
|
||||
|
||||
static func consecutiveTrumps<T>(in cards: T, for game: GameType) -> Int where T: Sequence, T.Element == Card {
|
||||
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 cards.contains(trumpsInOrder[count]) {
|
||||
count += 1
|
||||
}
|
||||
guard count >= 3 else {
|
||||
return 0
|
||||
}
|
||||
return count
|
||||
}
|
||||
}
|
@ -264,4 +264,18 @@ func routes(_ app: Application) throws {
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
app.post("deal") { req -> String in
|
||||
guard let token = req.body.string else {
|
||||
throw Abort(.badRequest)
|
||||
}
|
||||
switch database.dealCards(playerToken: token) {
|
||||
case .success:
|
||||
return ""
|
||||
case .invalidToken:
|
||||
throw Abort(.unauthorized) // 401
|
||||
default:
|
||||
throw Abort(.preconditionFailed) // 412
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user