Player actions, playable cards
This commit is contained in:
parent
50a35ece03
commit
20d1ce24da
@ -8,4 +8,8 @@ extension Array {
|
||||
}
|
||||
return Array(self[index..<count] + self[0..<index])
|
||||
}
|
||||
|
||||
func sorted<T>(by converting: (Element) -> T) -> [Element] where T: Comparable {
|
||||
sorted { converting($0) < converting($1) }
|
||||
}
|
||||
}
|
||||
|
8
Sources/App/Infos/CardInfo.swift
Normal file
8
Sources/App/Infos/CardInfo.swift
Normal file
@ -0,0 +1,8 @@
|
||||
import Foundation
|
||||
|
||||
struct CardInfo: Codable, Equatable {
|
||||
|
||||
let card: CardId
|
||||
|
||||
let playable: Bool
|
||||
}
|
@ -76,7 +76,7 @@ final class Database {
|
||||
- Parameter isPublic: Indicates that this is a game joinable by everyone
|
||||
- Returns: The table id
|
||||
*/
|
||||
func createTable(named name: TableName, player: PlayerName, isPublic: Bool) -> TableId {
|
||||
func createTable(named name: TableName, player: PlayerName, isPublic: Bool) -> TableInfo {
|
||||
tables.createTable(named: name, player: player, isPublic: isPublic)
|
||||
}
|
||||
|
||||
@ -105,4 +105,11 @@ final class Database {
|
||||
}
|
||||
return tables.performAction(player: player, action: action)
|
||||
}
|
||||
|
||||
func play(card: Card, playerToken: SessionToken) -> PlayCardResult {
|
||||
guard let player = players.registeredPlayerExists(withSessionToken: playerToken) else {
|
||||
return .invalidToken
|
||||
}
|
||||
return tables.play(card: card, player: player)
|
||||
}
|
||||
}
|
||||
|
@ -90,12 +90,12 @@ final class TableManagement: DiskWriter {
|
||||
- Parameter isPublic: Indicates that this is a game joinable by everyone
|
||||
- Returns: The table id
|
||||
*/
|
||||
func createTable(named name: TableName, player: PlayerName, isPublic: Bool) -> TableId {
|
||||
func createTable(named name: TableName, player: PlayerName, isPublic: Bool) -> TableInfo {
|
||||
let table = Table(newTable: name, isPublic: isPublic)
|
||||
_ = table.add(player: name)
|
||||
_ = table.add(player: player)
|
||||
tables[table.id] = table
|
||||
writeTableToDisk(table: table)
|
||||
return table.id
|
||||
return table.compileInfo(for: player)!
|
||||
}
|
||||
|
||||
/// A list of all public tables
|
||||
@ -171,4 +171,11 @@ final class TableManagement: DiskWriter {
|
||||
}
|
||||
return table.perform(action: action, forPlayer: player)
|
||||
}
|
||||
|
||||
func play(card: Card, player: PlayerName) -> PlayCardResult {
|
||||
guard let table = currentTable(for: player) else {
|
||||
return .noTableJoined
|
||||
}
|
||||
return table.play(card: card, player: player)
|
||||
}
|
||||
}
|
||||
|
@ -1,294 +1,5 @@
|
||||
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 {
|
||||
|
||||
/**
|
||||
@ -298,7 +9,7 @@ struct Dealer {
|
||||
// Select 16 random cards for the first hands
|
||||
Array(Card.allCards.shuffled()[0..<16])
|
||||
.split(intoChunksOf: 4)
|
||||
.map { $0.sorted(cardOrder: .normal) }
|
||||
.map { $0.sortedCards(order: NormalCardOrder.self) }
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,70 +0,0 @@
|
||||
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 = Array(leaders.map { cards[$0] }.joined())
|
||||
.consecutiveTrumps(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
|
||||
}
|
||||
}
|
@ -13,9 +13,15 @@ enum GamePhase: String, Codable {
|
||||
|
||||
/// The game negotiation is ongoing
|
||||
case bidding = "bidding"
|
||||
|
||||
/// The game must be selected by the player
|
||||
case selectGame = "select"
|
||||
|
||||
/// The game is in progress
|
||||
case playing = "play"
|
||||
|
||||
/// The player must select a card to give to the wedding offerer
|
||||
case selectWeddingCard = "wedding"
|
||||
|
||||
/// The game is over
|
||||
case gameFinished = "done"
|
||||
|
@ -4,19 +4,24 @@ enum GameType: Codable {
|
||||
|
||||
enum GameClass: Int {
|
||||
case ruf = 1
|
||||
case hochzeit = 2
|
||||
case bettel = 3
|
||||
case wenzGeier = 4
|
||||
case solo = 5
|
||||
case bettel = 2
|
||||
case wenzGeier = 3
|
||||
case solo = 4
|
||||
|
||||
var cost: Int {
|
||||
switch self {
|
||||
case .ruf: return 5
|
||||
case .hochzeit: return 10
|
||||
case .bettel: return 15
|
||||
case .wenzGeier, .solo: return 20
|
||||
}
|
||||
}
|
||||
|
||||
mutating func increase() {
|
||||
guard self != .solo else {
|
||||
return
|
||||
}
|
||||
self = .init(rawValue: rawValue + 1)!
|
||||
}
|
||||
}
|
||||
|
||||
case rufEichel
|
||||
@ -33,10 +38,8 @@ enum GameType: Codable {
|
||||
|
||||
var gameClass: GameClass {
|
||||
switch self {
|
||||
case .rufEichel, .rufBlatt, .rufSchelln:
|
||||
case .rufEichel, .rufBlatt, .rufSchelln, .hochzeit:
|
||||
return .ruf
|
||||
case .hochzeit:
|
||||
return .hochzeit
|
||||
case .bettel:
|
||||
return .bettel
|
||||
case .wenz, .geier:
|
||||
@ -45,6 +48,28 @@ enum GameType: Codable {
|
||||
return .solo
|
||||
}
|
||||
}
|
||||
|
||||
var isCall: Bool {
|
||||
switch self {
|
||||
case .rufEichel, .rufBlatt, .rufSchelln:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
var calledSuit: Card.Suit? {
|
||||
switch self {
|
||||
case .rufEichel:
|
||||
return .eichel
|
||||
case .rufBlatt:
|
||||
return .blatt
|
||||
case .rufSchelln:
|
||||
return .schelln
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var isSingleGame: Bool {
|
||||
switch self {
|
||||
|
@ -4,6 +4,15 @@ 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 Player {
|
||||
|
||||
let name: PlayerName
|
||||
@ -45,6 +54,14 @@ final class Player {
|
||||
var wonTricks: [Trick] = []
|
||||
|
||||
var socket: WebSocket? = nil
|
||||
|
||||
var canOfferWedding: Bool {
|
||||
rawCards.canOfferWedding
|
||||
}
|
||||
|
||||
var offersWedding = false
|
||||
|
||||
var wouldAcceptWedding = false
|
||||
|
||||
init(name: PlayerName) {
|
||||
self.name = name
|
||||
@ -53,7 +70,23 @@ final class Player {
|
||||
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
|
||||
}
|
||||
func connect(using socket: WebSocket) {
|
||||
_ = self.socket?.close()
|
||||
self.socket = socket
|
||||
@ -75,69 +108,286 @@ final class Player {
|
||||
self.socket = nil
|
||||
return true
|
||||
}
|
||||
|
||||
func canPerform(_ action: Action) -> Bool {
|
||||
actions.contains(action)
|
||||
}
|
||||
|
||||
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)
|
||||
.sortedCards(order: NormalCardOrder.self)
|
||||
.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 = []
|
||||
}
|
||||
if canOfferWedding {
|
||||
actions.append(.offerWedding)
|
||||
}
|
||||
}
|
||||
|
||||
func offerWedding() {
|
||||
offersWedding = true
|
||||
actions = []
|
||||
}
|
||||
|
||||
func weddingOfferExists() {
|
||||
guard isStillBidding else {
|
||||
return
|
||||
}
|
||||
actions = [.increaseOrMatchGame, .withdrawFromAuction]
|
||||
}
|
||||
|
||||
func hasWeddingOffer() {
|
||||
guard isStillBidding else {
|
||||
return
|
||||
}
|
||||
actions = [.acceptWedding, .increaseOrMatchGame, .withdrawFromAuction]
|
||||
}
|
||||
|
||||
func weddingOutbid() {
|
||||
isNextActor = false
|
||||
guard isStillBidding else {
|
||||
return
|
||||
}
|
||||
actions = []
|
||||
isStillBidding = true
|
||||
isGameLeader = false // Not relevant in this phase
|
||||
numberOfRaises = 0 // Not relevant in this phase
|
||||
if offersWedding {
|
||||
offersWedding = false
|
||||
isStillBidding = false
|
||||
}
|
||||
}
|
||||
|
||||
func didPerformBid() {
|
||||
isNextActor = false
|
||||
actions = []
|
||||
}
|
||||
|
||||
func requiresBid() {
|
||||
isNextActor = true
|
||||
actions = [.increaseOrMatchGame, .withdrawFromAuction]
|
||||
}
|
||||
|
||||
func acceptWedding() {
|
||||
wouldAcceptWedding = true
|
||||
actions = []
|
||||
}
|
||||
|
||||
func weddingAccepted() {
|
||||
guard isStillBidding else {
|
||||
actions = []
|
||||
return
|
||||
}
|
||||
actions = [.increaseOrMatchGame, .withdrawFromAuction]
|
||||
}
|
||||
|
||||
func auctionEnded() {
|
||||
actions = []
|
||||
isStillBidding = 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 mustSelectGame() {
|
||||
isNextActor = 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 gameStarts() {
|
||||
isNextActor = playsFirstCard
|
||||
startedCurrentTrick = playsFirstCard
|
||||
actions = [.doubleDuringGame]
|
||||
isGameLeader = false
|
||||
}
|
||||
|
||||
func switchLeadership() {
|
||||
isGameLeader.toggle()
|
||||
if isGameLeader {
|
||||
actions = actions.filter { $0 != .doubleDuringGame }
|
||||
} else if !actions.contains(.doubleDuringGame) {
|
||||
actions.append(.doubleDuringGame)
|
||||
}
|
||||
}
|
||||
|
||||
func withdrawFromBidding() {
|
||||
isStillBidding = false
|
||||
actions = []
|
||||
}
|
||||
|
||||
func didFinishTrick(canDoubleInNextRound: Bool) {
|
||||
isNextActor = false
|
||||
playedCard = nil
|
||||
wonTricks = []
|
||||
if canDoubleInNextRound, !isGameLeader {
|
||||
actions = [.doubleDuringGame]
|
||||
}
|
||||
}
|
||||
|
||||
func didWin(trick: Trick) {
|
||||
self.wonTricks.append(trick)
|
||||
self.isNextActor = true
|
||||
}
|
||||
|
||||
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 first = trick.first else {
|
||||
setPlayableCardsForStarter(game: game)
|
||||
return
|
||||
}
|
||||
|
||||
let sorter = game.sortingType
|
||||
|
||||
guard sorter.isTrump(first) else {
|
||||
setPlayableCardsFollowing(suit: first.suit, game: game)
|
||||
return
|
||||
}
|
||||
guard !sorter.hasTrump(in: cards) else {
|
||||
// Must follow with trump
|
||||
handCards = cards.map {
|
||||
.init(card: $0, isPlayable: sorter.isTrump($0))
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
private func setPlayableCardsFollowing(suit: Card.Suit, game: GameType) {
|
||||
let cards = rawCards
|
||||
let sorter = game.sortingType
|
||||
// No calling game, allow all cards of same suit
|
||||
let suitCards = sorter.cards(with: suit, in: cards)
|
||||
|
||||
func followSuit() {
|
||||
handCards = cards.map {
|
||||
.init(card: $0, isPlayable: !sorter.isTrump($0) && $0.suit == suit)
|
||||
}
|
||||
}
|
||||
guard let called = game.calledSuit else {
|
||||
if suitCards.isEmpty {
|
||||
// Can play any card
|
||||
setAllCards(playable: true)
|
||||
} else {
|
||||
// Must follow suit
|
||||
followSuit()
|
||||
}
|
||||
return
|
||||
}
|
||||
let ace = Card(called, .ass)
|
||||
guard called == suit else {
|
||||
if suitCards.isEmpty {
|
||||
// Exclude called ace, all others allowed
|
||||
handCards = cards.map {
|
||||
.init(card: $0, isPlayable: $0 != ace)
|
||||
}
|
||||
} else {
|
||||
// Must follow suit (called ace automatically excluded)
|
||||
followSuit()
|
||||
}
|
||||
return
|
||||
}
|
||||
// The called suit is player, 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) }
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
func info(masked: Bool) -> PlayerInfo {
|
||||
@ -193,3 +443,10 @@ extension Player {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Player: Equatable {
|
||||
|
||||
static func == (lhs: Player, rhs: Player) -> Bool {
|
||||
lhs.name == rhs.name
|
||||
}
|
||||
}
|
||||
|
@ -22,10 +22,7 @@ final class Table {
|
||||
|
||||
var gameType: GameType? = nil
|
||||
|
||||
var minimumPlayableGame: GameType.GameClass = .ruf
|
||||
|
||||
/// Indicates if doubles are still allowed
|
||||
var canDoubleDuringGame = false
|
||||
var minimumPlayableGame: GameType.GameClass?
|
||||
|
||||
/// Indicates if any player doubled during the current round, extending it to the next round
|
||||
var didDoubleInCurrentRound = false
|
||||
@ -34,6 +31,42 @@ final class Table {
|
||||
var allPlayersFinishedDoubling: Bool {
|
||||
!players.contains { $0.didDoubleAfterFourCards == nil }
|
||||
}
|
||||
|
||||
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: Player {
|
||||
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 }
|
||||
}
|
||||
|
||||
init(id: TableId, name: TableName, isPublic: Bool) {
|
||||
self.id = id
|
||||
@ -63,6 +96,11 @@ final class Table {
|
||||
func contains(player: PlayerName) -> Bool {
|
||||
players.contains { $0.name == player }
|
||||
}
|
||||
|
||||
/// The player to play the first card of the current game
|
||||
var firstPlayer: Player {
|
||||
players.first { $0.playsFirstCard }!
|
||||
}
|
||||
|
||||
func select(player: PlayerName) -> Player? {
|
||||
players.first { $0.name == player }
|
||||
@ -86,6 +124,29 @@ final class Table {
|
||||
}
|
||||
return players[index]
|
||||
}
|
||||
|
||||
func index(of player: Player) -> Int {
|
||||
players.firstIndex(of: player)!
|
||||
}
|
||||
|
||||
func nextPlayer(after player: Player) -> Player {
|
||||
let i = index(of: player)
|
||||
let newIndex = (i + 1) % maximumPlayersPerTable
|
||||
return players[newIndex]
|
||||
}
|
||||
|
||||
func nextBidder(after player: Player) -> Player {
|
||||
// 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 else {
|
||||
continue
|
||||
}
|
||||
return player
|
||||
}
|
||||
return player
|
||||
}
|
||||
|
||||
func remove(player: PlayerName) {
|
||||
guard contains(player: player) else {
|
||||
@ -116,11 +177,10 @@ final class Table {
|
||||
}
|
||||
|
||||
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
|
||||
phase = .waitingForPlayers
|
||||
gameType = nil
|
||||
minimumPlayableGame = nil // 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].prepareForFirstGame(isFirstPlayer: i == index)
|
||||
@ -138,8 +198,50 @@ final class Table {
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return .invalidCard
|
||||
}
|
||||
player.play(card: card)
|
||||
if let completedTrick = completedTrick {
|
||||
didFinish(trick: completedTrick, in: game)
|
||||
} else {
|
||||
let next = nextPlayer(after: player)
|
||||
next.isNextActor = true
|
||||
player.isNextActor = false
|
||||
}
|
||||
updatePlayableCards()
|
||||
return .success
|
||||
}
|
||||
|
||||
private func updatePlayableCards() {
|
||||
let playedCards = currentTrick
|
||||
players.forEach {
|
||||
$0.setPlayableCards(forCurrentTrick: playedCards, in: gameType)
|
||||
}
|
||||
}
|
||||
|
||||
func didFinish(trick: Trick, in game: GameType) {
|
||||
// If trick is completed, calculate winner
|
||||
let index = trick.highCardIndex(forGame: game)
|
||||
players.forEach {
|
||||
$0.didFinishTrick(canDoubleInNextRound: didDoubleInCurrentRound)
|
||||
}
|
||||
players[index].didWin(trick: trick)
|
||||
didDoubleInCurrentRound = false
|
||||
}
|
||||
|
||||
func perform(action: Player.Action, forPlayer player: PlayerName) -> PlayerActionResult {
|
||||
let player = select(player: player)!
|
||||
guard player.canPerform(action) else {
|
||||
return .tableStateInvalid
|
||||
}
|
||||
defer { sendUpdateToAllPlayers() }
|
||||
switch action {
|
||||
case .deal:
|
||||
@ -149,15 +251,15 @@ final class Table {
|
||||
case .noDoubleCost:
|
||||
return perform(double: false, forPlayer: player)
|
||||
case .offerWedding:
|
||||
fatalError()
|
||||
return performWeddingCall(forPlayer: player)
|
||||
case .acceptWedding:
|
||||
fatalError()
|
||||
return handleWeddingAccept(forPlayer: player)
|
||||
case .increaseOrMatchGame:
|
||||
fatalError()
|
||||
return performBidIncrease(forPlayer: player)
|
||||
case .withdrawFromAuction:
|
||||
fatalError()
|
||||
return performWithdrawl(forPlayer: player)
|
||||
case .doubleDuringGame:
|
||||
fatalError()
|
||||
return performDoubleDuringGame(forPlayer: player)
|
||||
}
|
||||
}
|
||||
|
||||
@ -169,19 +271,16 @@ final class Table {
|
||||
return .tableStateInvalid
|
||||
}
|
||||
|
||||
phase = .collectingDoubles
|
||||
gameType = nil
|
||||
minimumPlayableGame = .ruf
|
||||
|
||||
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 name: PlayerName) -> PlayerActionResult {
|
||||
let player = select(player: player)!
|
||||
func perform(double: Bool, forPlayer player: Player) -> PlayerActionResult {
|
||||
player.didDouble(double)
|
||||
if allPlayersFinishedDoubling {
|
||||
dealAdditionalCards()
|
||||
@ -194,18 +293,173 @@ final class Table {
|
||||
for (index, player) in players.enumerated() {
|
||||
player.assignRemainingCards(cards[index])
|
||||
}
|
||||
players.forEach { $0.startAuction() }
|
||||
minimumPlayableGame = nil
|
||||
}
|
||||
|
||||
private func performWeddingCall(forPlayer player: Player) -> PlayerActionResult {
|
||||
guard phase == .bidding else {
|
||||
return .tableStateInvalid
|
||||
}
|
||||
guard minimumPlayableGame == nil || minimumPlayableGame == .ruf else {
|
||||
return .tableStateInvalid
|
||||
}
|
||||
guard player.offersWedding else {
|
||||
return .tableStateInvalid
|
||||
}
|
||||
guard !weddingOfferExists else {
|
||||
// Only one wedding allowed at the table
|
||||
return .tableStateInvalid
|
||||
}
|
||||
// Only allow wedding acceptance or outbidding
|
||||
players.forEach { $0.weddingOfferExists() }
|
||||
player.offerWedding()
|
||||
firstPlayer.hasWeddingOffer()
|
||||
minimumPlayableGame = .bettel
|
||||
return .success
|
||||
}
|
||||
|
||||
private func startAuction() {
|
||||
players.forEach { $0.startAuction() }
|
||||
minimumPlayableGame = .ruf
|
||||
|
||||
private func performBidIncrease(forPlayer player: Player) -> 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
|
||||
}
|
||||
if minimumPlayableGame == nil {
|
||||
minimumPlayableGame = .ruf
|
||||
} else {
|
||||
minimumPlayableGame!.increase()
|
||||
}
|
||||
player.didPerformBid()
|
||||
// Find next player to place bid
|
||||
nextBidder(after: player).requiresBid()
|
||||
return .success
|
||||
}
|
||||
|
||||
|
||||
private func handleWeddingOutbid(forPlayer player: Player) -> PlayerActionResult {
|
||||
if player.offersWedding {
|
||||
// A player offering a wedding can't outbid itself
|
||||
return .tableStateInvalid
|
||||
}
|
||||
players.forEach { $0.weddingOutbid() }
|
||||
firstPlayer.requiresBid()
|
||||
return .success
|
||||
}
|
||||
|
||||
private func handleWeddingAccept(forPlayer player: Player) -> PlayerActionResult {
|
||||
guard phase == .bidding else {
|
||||
return .tableStateInvalid
|
||||
}
|
||||
guard minimumPlayableGame == nil || minimumPlayableGame == .ruf 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()
|
||||
return .success
|
||||
}
|
||||
|
||||
private func selectedWedding(player: Player) {
|
||||
minimumPlayableGame = nil
|
||||
gameType = .hochzeit
|
||||
phase = .selectWeddingCard
|
||||
players.forEach { $0.auctionEnded() }
|
||||
player.mustSelectWeddingCard()
|
||||
}
|
||||
|
||||
private func selectedCardForWedding(card: Card, player: Player) -> PlayCardResult {
|
||||
guard player.isNextActor,
|
||||
player.wouldAcceptWedding,
|
||||
weddingOfferExists else {
|
||||
return .invalidTableState
|
||||
}
|
||||
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.gameStarts() }
|
||||
player.switchLeadership()
|
||||
offerer.switchLeadership()
|
||||
return .success
|
||||
}
|
||||
|
||||
private func performWithdrawl(forPlayer player: Player) -> PlayerActionResult {
|
||||
guard phase == .bidding,
|
||||
player.isNextActor,
|
||||
player.isStillBidding else {
|
||||
return .tableStateInvalid
|
||||
}
|
||||
player.withdrawFromBidding()
|
||||
switch numberOfRemainingBidders {
|
||||
case 1:
|
||||
selectGame(player: auctionWinner)
|
||||
case 0:
|
||||
// All players withdrawn, deal new cards
|
||||
let first = firstPlayer
|
||||
let newPlayer = self.nextBidder(after: first)
|
||||
first.playsFirstCard = false
|
||||
newPlayer.playsFirstCard = true
|
||||
prepareTableForFirstGame()
|
||||
return dealInitialCards()
|
||||
default:
|
||||
break
|
||||
}
|
||||
return .success
|
||||
}
|
||||
|
||||
private func selectGame(player: Player) {
|
||||
minimumPlayableGame = nil
|
||||
gameType = nil
|
||||
phase = .selectGame
|
||||
players.forEach { $0.auctionEnded() }
|
||||
player.mustSelectGame()
|
||||
}
|
||||
|
||||
private func performDoubleDuringGame(forPlayer player: Player) -> PlayerActionResult {
|
||||
guard phase == .playing,
|
||||
!player.isGameLeader else {
|
||||
return .tableStateInvalid
|
||||
}
|
||||
player.numberOfRaises += 1
|
||||
players.forEach { $0.switchLeadership() }
|
||||
return .success
|
||||
}
|
||||
|
||||
private func reset() {
|
||||
phase = .waitingForPlayers
|
||||
gameType = nil
|
||||
minimumPlayableGame = .ruf
|
||||
minimumPlayableGame = nil
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,16 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
typealias Trick = [Card]
|
||||
|
||||
extension Trick {
|
||||
|
||||
func winnerIndex(forGameType type: GameType) -> Int {
|
||||
let highCard = sorted(cardOrder: type.sortingType).first!
|
||||
return firstIndex(of: highCard)!
|
||||
}
|
||||
|
||||
var points: Int {
|
||||
map { $0.points }
|
||||
.reduce(0, +)
|
||||
}
|
||||
}
|
14
Sources/App/Results/PlayCardResult.swift
Normal file
14
Sources/App/Results/PlayCardResult.swift
Normal file
@ -0,0 +1,14 @@
|
||||
import Foundation
|
||||
|
||||
enum PlayCardResult {
|
||||
|
||||
case success
|
||||
|
||||
case invalidToken
|
||||
|
||||
case noTableJoined
|
||||
|
||||
case invalidTableState
|
||||
|
||||
case invalidCard
|
||||
}
|
@ -203,8 +203,8 @@ func routes(_ app: Application) throws {
|
||||
guard let player = database.registeredPlayerExists(withSessionToken: token) else {
|
||||
throw Abort(.unauthorized) // 401
|
||||
}
|
||||
let tableId = database.createTable(named: tableName, player: player, isPublic: isPublic)
|
||||
return tableId
|
||||
let table = database.createTable(named: tableName, player: player, isPublic: isPublic)
|
||||
return try encodeJSON(table)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -293,4 +293,25 @@ func routes(_ app: Application) throws {
|
||||
throw Abort(.preconditionFailed) // 412
|
||||
}
|
||||
}
|
||||
|
||||
app.post("player", "card", ":card") { req -> String in
|
||||
guard let token = req.body.string,
|
||||
let cardId = req.parameters.get("card"),
|
||||
let card = Card(id: cardId) else {
|
||||
throw Abort(.badRequest)
|
||||
}
|
||||
switch database.play(card: card, playerToken: token) {
|
||||
case .success:
|
||||
return ""
|
||||
case .invalidToken:
|
||||
throw Abort(.unauthorized) // 401
|
||||
case .noTableJoined:
|
||||
throw Abort(.preconditionFailed) // 412
|
||||
case .invalidTableState:
|
||||
throw Abort(.preconditionFailed) // 412
|
||||
case .invalidCard:
|
||||
throw Abort(.preconditionFailed) // 412
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user