Add card dealing

This commit is contained in:
Christoph Hagen 2021-12-01 22:49:54 +01:00
parent 1ee3fbc0ab
commit cdf079698e
10 changed files with 499 additions and 8 deletions

View File

@ -1,10 +1,19 @@
import Foundation 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 /// The cards for a player
let cards: [CardId] let cards: [HandCard]
/// Indicates if the card can be played // The cards on the table, as seen from the players perspective
let playable: [Bool] let tableCards: [CardId]
} }

View File

@ -1,6 +1,6 @@
import Foundation import Foundation
enum GameType { enum GameType: Codable {
case rufEichel case rufEichel
case rufBlatt case rufBlatt
@ -52,5 +52,37 @@ enum GameType {
return 20 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
}

View File

@ -1,14 +1,28 @@
import Foundation import Foundation
struct TableInfo: Codable { struct TableInfo: ClientMessage {
static let type: ClientMessageType = .tableInfo
let id: String let id: String
let name: 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 { extension TableInfo: Comparable {

View 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
}
}

View 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"
}

View 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, +)
}
}

View File

@ -0,0 +1,15 @@
import Foundation
enum DealCardResult {
case success
case invalidToken
case noTableJoined
case tableNotFull
case tableStateInvalid
}

View 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
}
}

View File

@ -264,4 +264,18 @@ func routes(_ app: Application) throws {
} }
return "" 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
}
}
} }