Play cards, select game, player actions
This commit is contained in:
parent
ca7fc858c2
commit
fa3aaadef8
@ -8,17 +8,27 @@ struct PlayerInfo: Codable, Equatable {
|
||||
|
||||
/// The player is the next one to perform an action
|
||||
let active: Bool
|
||||
|
||||
let selectsGame: Bool
|
||||
|
||||
/// The cards in the hand of the player
|
||||
let cards: [CardInfo]
|
||||
|
||||
/// The action the player can perform
|
||||
let actions: [String]
|
||||
|
||||
let playedCard: CardId?
|
||||
|
||||
/// The height of the player card on the table stack
|
||||
let position: Int
|
||||
|
||||
init(player: Player, isMasked: Bool) {
|
||||
init(player: Player, isMasked: Bool, trickPosition: Int) {
|
||||
self.name = player.name
|
||||
self.connected = player.isConnected
|
||||
self.active = player.isNextActor
|
||||
self.selectsGame = player.selectsGame
|
||||
self.playedCard = player.playedCard?.id
|
||||
self.position = trickPosition
|
||||
if isMasked {
|
||||
self.cards = []
|
||||
self.actions = []
|
||||
|
@ -13,14 +13,24 @@ struct TableInfo: Codable {
|
||||
let playerAcross: PlayerInfo?
|
||||
|
||||
let playerRight: PlayerInfo?
|
||||
|
||||
let playableGames: [GameId]
|
||||
|
||||
init(_ table: Table, forPlayerAt playerIndex: Int) {
|
||||
let player = table.player(at: playerIndex)!
|
||||
self.id = table.id
|
||||
self.name = table.name
|
||||
self.player = table.player(at: playerIndex)!.info(masked: false)
|
||||
self.playerLeft = table.player(leftOf: playerIndex)?.info(masked: true)
|
||||
self.playerAcross = table.player(acrossOf: playerIndex)?.info(masked: true)
|
||||
self.playerRight = table.player(rightOf: playerIndex)?.info(masked: true)
|
||||
self.player = table.playerInfo(at: playerIndex, masked: false)!
|
||||
self.playerLeft = table.playerInfo(leftOf: playerIndex, masked: true)
|
||||
self.playerAcross = table.playerInfo(acrossOf: playerIndex, masked: true)
|
||||
self.playerRight = table.playerInfo(rightOf: playerIndex, masked: true)
|
||||
|
||||
if table.phase == .selectGame, player.selectsGame {
|
||||
let games = table.minimumPlayableGame?.availableGames ?? GameType.allCases
|
||||
self.playableGames = games.filter(player.canPlay).map { $0.id }
|
||||
} else {
|
||||
self.playableGames = []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -106,6 +106,13 @@ final class Database {
|
||||
return tables.performAction(player: player, action: action)
|
||||
}
|
||||
|
||||
func select(game: GameType, playerToken: SessionToken) -> PlayerActionResult {
|
||||
guard let player = players.registeredPlayerExists(withSessionToken: playerToken) else {
|
||||
return .invalidToken
|
||||
}
|
||||
return tables.select(game: game, player: player)
|
||||
}
|
||||
|
||||
func play(card: Card, playerToken: SessionToken) -> PlayCardResult {
|
||||
guard let player = players.registeredPlayerExists(withSessionToken: playerToken) else {
|
||||
return .invalidToken
|
||||
|
@ -167,11 +167,20 @@ final class TableManagement: DiskWriter {
|
||||
|
||||
func performAction(player: PlayerName, action: Player.Action) -> PlayerActionResult {
|
||||
guard let table = currentTable(for: player) else {
|
||||
print("Player \(player) wants to \(action.path), but no table joined")
|
||||
return .noTableJoined
|
||||
}
|
||||
return table.perform(action: action, forPlayer: player)
|
||||
}
|
||||
|
||||
func select(game: GameType, player: PlayerName) -> PlayerActionResult {
|
||||
guard let table = currentTable(for: player) else {
|
||||
print("Player \(player) wants to play \(game.rawValue), but no table joined")
|
||||
return .noTableJoined
|
||||
}
|
||||
return table.select(game: game, player: player)
|
||||
}
|
||||
|
||||
func play(card: Card, player: PlayerName) -> PlayCardResult {
|
||||
guard let table = currentTable(for: player) else {
|
||||
return .noTableJoined
|
||||
|
@ -26,7 +26,7 @@ extension CardOrder {
|
||||
|
||||
static func highCardIndex(cards: [Card]) -> Int {
|
||||
let high: Card
|
||||
if isTrump(cards[0]) {
|
||||
if hasTrump(in: cards) {
|
||||
high = sort(cards).first!
|
||||
} else {
|
||||
let suit = cards.first!.suit
|
||||
@ -66,4 +66,8 @@ extension CardOrder {
|
||||
static func cards(with suit: Card.Suit, in cards: [Card]) -> [Card] {
|
||||
cards.filter { !isTrump($0) && $0.suit == suit }
|
||||
}
|
||||
|
||||
static func hasCardToCall(_ suit: Card.Suit, in cards: [Card]) -> Bool {
|
||||
cards.contains { $0.symbol != .ass && $0.suit == suit && !isTrump($0) }
|
||||
}
|
||||
}
|
||||
|
61
Sources/App/Model/GameClass.swift
Normal file
61
Sources/App/Model/GameClass.swift
Normal file
@ -0,0 +1,61 @@
|
||||
import Foundation
|
||||
|
||||
typealias GameId = String
|
||||
|
||||
extension GameType {
|
||||
|
||||
var gameClass: GameClass {
|
||||
switch self {
|
||||
case .rufEichel, .rufBlatt, .rufSchelln, .hochzeit:
|
||||
return .ruf
|
||||
case .bettel:
|
||||
return .bettel
|
||||
case .wenz, .geier:
|
||||
return .wenzGeier
|
||||
case .soloEichel, .soloBlatt, .soloHerz, .soloSchelln:
|
||||
return .solo
|
||||
}
|
||||
}
|
||||
|
||||
enum GameClass: Int {
|
||||
case ruf = 1
|
||||
case bettel = 2
|
||||
case wenzGeier = 3
|
||||
case solo = 4
|
||||
|
||||
var cost: Int {
|
||||
switch self {
|
||||
case .ruf: return 5
|
||||
case .bettel: return 15
|
||||
case .wenzGeier, .solo: return 20
|
||||
}
|
||||
}
|
||||
|
||||
mutating func increase() {
|
||||
guard self != .solo else {
|
||||
return
|
||||
}
|
||||
self = .init(rawValue: rawValue + 1)!
|
||||
}
|
||||
|
||||
var availableGames: [GameType] {
|
||||
switch self {
|
||||
case .ruf:
|
||||
return GameType.allCases
|
||||
case .bettel:
|
||||
return [.bettel, .wenz, .geier, .soloEichel, .soloBlatt, .soloHerz, .soloSchelln]
|
||||
case .wenzGeier:
|
||||
return [.wenz, .geier, .soloEichel, .soloBlatt, .soloHerz, .soloSchelln]
|
||||
case .solo:
|
||||
return [.soloEichel, .soloBlatt, .soloHerz, .soloSchelln]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension GameType.GameClass: Comparable {
|
||||
|
||||
static func < (lhs: GameType.GameClass, rhs: GameType.GameClass) -> Bool {
|
||||
lhs.rawValue < rhs.rawValue
|
||||
}
|
||||
}
|
@ -1,53 +1,18 @@
|
||||
import Foundation
|
||||
|
||||
enum GameType: Codable {
|
||||
enum GameType: String, CaseIterable, Codable {
|
||||
|
||||
enum GameClass: Int {
|
||||
case ruf = 1
|
||||
case bettel = 2
|
||||
case wenzGeier = 3
|
||||
case solo = 4
|
||||
|
||||
var cost: Int {
|
||||
switch self {
|
||||
case .ruf: return 5
|
||||
case .bettel: return 15
|
||||
case .wenzGeier, .solo: return 20
|
||||
}
|
||||
}
|
||||
|
||||
mutating func increase() {
|
||||
guard self != .solo else {
|
||||
return
|
||||
}
|
||||
self = .init(rawValue: rawValue + 1)!
|
||||
}
|
||||
}
|
||||
|
||||
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, .hochzeit:
|
||||
return .ruf
|
||||
case .bettel:
|
||||
return .bettel
|
||||
case .wenz, .geier:
|
||||
return .wenzGeier
|
||||
case .soloEichel, .soloBlatt, .soloHerz, .soloSchelln:
|
||||
return .solo
|
||||
}
|
||||
}
|
||||
case rufEichel = "ruf-eichel"
|
||||
case rufBlatt = "ruf-blatt"
|
||||
case rufSchelln = "ruf-schelln"
|
||||
case hochzeit = "hochzeit"
|
||||
case bettel = "bettel"
|
||||
case wenz = "wenz"
|
||||
case geier = "geier"
|
||||
case soloEichel = "solo-eichel"
|
||||
case soloBlatt = "solo-blatt"
|
||||
case soloHerz = "solo-herz"
|
||||
case soloSchelln = "solo-schelln"
|
||||
|
||||
var isCall: Bool {
|
||||
switch self {
|
||||
@ -70,7 +35,7 @@ enum GameType: Codable {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var isSingleGame: Bool {
|
||||
switch self {
|
||||
case .rufEichel, .rufBlatt, .rufSchelln, .hochzeit:
|
||||
@ -83,6 +48,10 @@ enum GameType: Codable {
|
||||
var basicCost: Int {
|
||||
gameClass.cost
|
||||
}
|
||||
|
||||
var id: GameId {
|
||||
rawValue
|
||||
}
|
||||
|
||||
var sortingType: CardOrder.Type {
|
||||
switch self {
|
||||
|
@ -86,7 +86,9 @@ final class Player {
|
||||
func play(card: Card) {
|
||||
remove(card: card)
|
||||
playedCard = card
|
||||
actions = actions.filter { $0 != .doubleDuringGame }
|
||||
}
|
||||
|
||||
func connect(using socket: WebSocket) {
|
||||
_ = self.socket?.close()
|
||||
self.socket = socket
|
||||
@ -113,11 +115,15 @@ final class Player {
|
||||
actions.contains(action)
|
||||
}
|
||||
|
||||
func prepareForFirstGame(isFirstPlayer: Bool) {
|
||||
func prepareForNewGame(isFirstPlayer: Bool) {
|
||||
playsFirstCard = isFirstPlayer
|
||||
isNextActor = isFirstPlayer
|
||||
selectsGame = false
|
||||
startedCurrentTrick = isFirstPlayer
|
||||
actions = [.deal]
|
||||
didDoubleAfterFourCards = nil
|
||||
isStillBidding = true
|
||||
isGameLeader = false
|
||||
handCards = []
|
||||
playedCard = nil
|
||||
wonTricks = []
|
||||
@ -152,6 +158,7 @@ final class Player {
|
||||
|
||||
func offerWedding() {
|
||||
offersWedding = true
|
||||
isStillBidding = false
|
||||
actions = []
|
||||
}
|
||||
|
||||
@ -171,14 +178,8 @@ final class Player {
|
||||
|
||||
func weddingOutbid() {
|
||||
isNextActor = false
|
||||
guard isStillBidding else {
|
||||
return
|
||||
}
|
||||
actions = []
|
||||
if offersWedding {
|
||||
offersWedding = false
|
||||
isStillBidding = false
|
||||
}
|
||||
offersWedding = false
|
||||
}
|
||||
|
||||
func didPerformBid() {
|
||||
@ -221,8 +222,26 @@ final class Player {
|
||||
numberOfRaises += 1
|
||||
}
|
||||
|
||||
func canPlay(game: GameType) -> Bool {
|
||||
guard let suit = game.calledSuit else {
|
||||
if game == .hochzeit {
|
||||
return canOfferWedding
|
||||
}
|
||||
return true
|
||||
}
|
||||
let sorter = game.sortingType
|
||||
let cards = rawCards
|
||||
guard sorter.hasCardToCall(suit, in: cards) else {
|
||||
// Player needs at least one card of the called suit
|
||||
return false
|
||||
}
|
||||
let ace = Card(suit, .ass)
|
||||
return !cards.contains(ace)
|
||||
}
|
||||
|
||||
func mustSelectGame() {
|
||||
isNextActor = true
|
||||
selectsGame = true
|
||||
}
|
||||
|
||||
func replace(card: Card, with other: Card) {
|
||||
@ -237,11 +256,15 @@ final class Player {
|
||||
return removed
|
||||
}
|
||||
|
||||
func gameStarts() {
|
||||
func start(game: GameType) {
|
||||
isNextActor = playsFirstCard
|
||||
startedCurrentTrick = playsFirstCard
|
||||
selectsGame = false
|
||||
actions = [.doubleDuringGame]
|
||||
isGameLeader = false
|
||||
if playsFirstCard {
|
||||
setPlayableCardsForStarter(game: game)
|
||||
}
|
||||
}
|
||||
|
||||
func switchLeadership() {
|
||||
@ -254,6 +277,7 @@ final class Player {
|
||||
}
|
||||
|
||||
func withdrawFromBidding() {
|
||||
isNextActor = false
|
||||
isStillBidding = false
|
||||
actions = []
|
||||
}
|
||||
@ -263,6 +287,8 @@ final class Player {
|
||||
playedCard = nil
|
||||
if canDoubleInNextRound, !isGameLeader {
|
||||
actions = [.doubleDuringGame]
|
||||
} else {
|
||||
actions = []
|
||||
}
|
||||
}
|
||||
|
||||
@ -390,8 +416,8 @@ final class Player {
|
||||
}
|
||||
}
|
||||
|
||||
func info(masked: Bool) -> PlayerInfo {
|
||||
.init(player: self, isMasked: masked)
|
||||
func info(masked: Bool, positionInTrick: Int) -> PlayerInfo {
|
||||
.init(player: self, isMasked: masked, trickPosition: positionInTrick)
|
||||
}
|
||||
}
|
||||
|
||||
@ -410,40 +436,6 @@ extension Player {
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Player: Equatable {
|
||||
|
||||
static func == (lhs: Player, rhs: Player) -> Bool {
|
||||
|
35
Sources/App/Model/PlayerAction.swift
Normal file
35
Sources/App/Model/PlayerAction.swift
Normal file
@ -0,0 +1,35 @@
|
||||
import Foundation
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
@ -32,6 +32,11 @@ final class Table {
|
||||
!players.contains { $0.didDoubleAfterFourCards == nil }
|
||||
}
|
||||
|
||||
/// At least one double exists after all players acted on their first cards
|
||||
var initialDoubleExists: Bool {
|
||||
players.contains { $0.didDoubleAfterFourCards == true }
|
||||
}
|
||||
|
||||
var weddingOfferExists: Bool {
|
||||
players.contains { $0.offersWedding }
|
||||
}
|
||||
@ -105,17 +110,24 @@ final class Table {
|
||||
func select(player: PlayerName) -> Player? {
|
||||
players.first { $0.name == player }
|
||||
}
|
||||
|
||||
func player(leftOf index: Int) -> Player? {
|
||||
player(at: (index + 1) % 4)
|
||||
|
||||
func playerInfo(at index: Int, masked: Bool) -> PlayerInfo? {
|
||||
let position = players.firstIndex { $0.startedCurrentTrick }!
|
||||
let layer = (index - position + 4) % 4
|
||||
//
|
||||
return player(at: index)?.info(masked: masked, positionInTrick: layer)
|
||||
}
|
||||
|
||||
func player(acrossOf index: Int) -> Player? {
|
||||
player(at: (index + 2) % 4)
|
||||
func playerInfo(leftOf index: Int, masked: Bool) -> PlayerInfo? {
|
||||
playerInfo(at: (index + 1) % 4, masked: masked)
|
||||
}
|
||||
|
||||
func player(rightOf index: Int) -> Player? {
|
||||
player(at: (index + 3) % 4)
|
||||
func playerInfo(acrossOf index: Int, masked: Bool) -> PlayerInfo? {
|
||||
playerInfo(at: (index + 2) % 4, masked: masked)
|
||||
}
|
||||
|
||||
func playerInfo(rightOf index: Int, masked: Bool) -> PlayerInfo? {
|
||||
playerInfo(at: (index + 3) % 4, masked: masked)
|
||||
}
|
||||
|
||||
func player(at index: Int) -> Player? {
|
||||
@ -140,7 +152,7 @@ final class Table {
|
||||
let index = index(of: player)
|
||||
for i in 1..<4 {
|
||||
let player = players[(index + i) % 4]
|
||||
guard player.isStillBidding else {
|
||||
guard player.isStillBidding, !player.offersWedding else {
|
||||
continue
|
||||
}
|
||||
return player
|
||||
@ -149,10 +161,14 @@ final class Table {
|
||||
}
|
||||
|
||||
func remove(player: PlayerName) {
|
||||
guard contains(player: player) else {
|
||||
guard let index = players.firstIndex(where: { $0.name == player }) else {
|
||||
return
|
||||
}
|
||||
players = players.filter { $0.name != player }
|
||||
let removedPlayer = players[index]
|
||||
if removedPlayer.playsFirstCard {
|
||||
players[(index + 1) % players.count].playsFirstCard = true
|
||||
}
|
||||
players.remove(at: index)
|
||||
reset()
|
||||
}
|
||||
|
||||
@ -183,7 +199,7 @@ final class Table {
|
||||
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)
|
||||
players[i].prepareForNewGame(isFirstPlayer: i == index)
|
||||
}
|
||||
}
|
||||
|
||||
@ -217,6 +233,7 @@ final class Table {
|
||||
player.isNextActor = false
|
||||
}
|
||||
updatePlayableCards()
|
||||
sendUpdateToAllPlayers()
|
||||
return .success
|
||||
}
|
||||
|
||||
@ -240,6 +257,7 @@ final class Table {
|
||||
func perform(action: Player.Action, forPlayer player: PlayerName) -> PlayerActionResult {
|
||||
let player = select(player: player)!
|
||||
guard player.canPerform(action) else {
|
||||
print("Player \(player) wants to \(action.path), but only allowed: \(player.actions)")
|
||||
return .tableStateInvalid
|
||||
}
|
||||
defer { sendUpdateToAllPlayers() }
|
||||
@ -282,9 +300,13 @@ final class Table {
|
||||
|
||||
func perform(double: Bool, forPlayer player: Player) -> PlayerActionResult {
|
||||
player.didDouble(double)
|
||||
if allPlayersFinishedDoubling {
|
||||
dealAdditionalCards()
|
||||
guard allPlayersFinishedDoubling else {
|
||||
return .success
|
||||
}
|
||||
guard initialDoubleExists else {
|
||||
return dealNextGame()
|
||||
}
|
||||
dealAdditionalCards()
|
||||
return .success
|
||||
}
|
||||
|
||||
@ -295,20 +317,25 @@ final class Table {
|
||||
}
|
||||
players.forEach { $0.startAuction() }
|
||||
minimumPlayableGame = nil
|
||||
phase = .bidding
|
||||
}
|
||||
|
||||
private func performWeddingCall(forPlayer player: Player) -> PlayerActionResult {
|
||||
guard phase == .bidding else {
|
||||
print("Invalid phase \(phase) for wedding call")
|
||||
return .tableStateInvalid
|
||||
}
|
||||
guard minimumPlayableGame == nil || minimumPlayableGame == .ruf else {
|
||||
print("Invalid minimum game \(minimumPlayableGame!) for wedding call")
|
||||
return .tableStateInvalid
|
||||
}
|
||||
guard player.offersWedding else {
|
||||
print("Player does not offer wedding")
|
||||
return .tableStateInvalid
|
||||
}
|
||||
guard !weddingOfferExists else {
|
||||
// Only one wedding allowed at the table
|
||||
print("Already one wedding at table")
|
||||
return .tableStateInvalid
|
||||
}
|
||||
// Only allow wedding acceptance or outbidding
|
||||
@ -334,6 +361,8 @@ final class Table {
|
||||
minimumPlayableGame = .ruf
|
||||
} else {
|
||||
minimumPlayableGame!.increase()
|
||||
// Remove wedding offers
|
||||
players.forEach { $0.weddingOutbid() }
|
||||
}
|
||||
player.didPerformBid()
|
||||
// Find next player to place bid
|
||||
@ -408,7 +437,7 @@ final class Table {
|
||||
|
||||
// Start the game
|
||||
gameType = .hochzeit
|
||||
players.forEach { $0.gameStarts() }
|
||||
players.forEach { $0.start(game: .hochzeit) }
|
||||
player.switchLeadership()
|
||||
offerer.switchLeadership()
|
||||
return .success
|
||||
@ -426,18 +455,22 @@ final class Table {
|
||||
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()
|
||||
return dealNextGame()
|
||||
default:
|
||||
break
|
||||
nextBidder(after: player).requiresBid()
|
||||
}
|
||||
return .success
|
||||
}
|
||||
|
||||
private func dealNextGame() -> PlayerActionResult {
|
||||
let first = firstPlayer
|
||||
let newPlayer = self.nextBidder(after: first)
|
||||
first.playsFirstCard = false
|
||||
newPlayer.playsFirstCard = true
|
||||
prepareTableForFirstGame()
|
||||
return dealInitialCards()
|
||||
}
|
||||
|
||||
private func selectGame(player: Player) {
|
||||
minimumPlayableGame = nil
|
||||
gameType = nil
|
||||
@ -446,11 +479,43 @@ final class Table {
|
||||
player.mustSelectGame()
|
||||
}
|
||||
|
||||
func select(game: GameType, player: PlayerName) -> PlayerActionResult {
|
||||
let player = select(player: player)!
|
||||
guard phase == .selectGame, player.selectsGame, game != .hochzeit else {
|
||||
return .tableStateInvalid
|
||||
}
|
||||
guard minimumPlayableGame == nil || game.gameClass >= minimumPlayableGame! else {
|
||||
return .tableStateInvalid
|
||||
}
|
||||
defer { sendUpdateToAllPlayers() }
|
||||
guard let suit = game.calledSuit else {
|
||||
phase = .playing
|
||||
gameType = game
|
||||
minimumPlayableGame = nil
|
||||
players.forEach { $0.start(game: game) }
|
||||
player.switchLeadership()
|
||||
|
||||
return .success
|
||||
}
|
||||
|
||||
guard player.canPlay(game: game) else {
|
||||
return .tableStateInvalid
|
||||
}
|
||||
phase = .playing
|
||||
gameType = game
|
||||
minimumPlayableGame = nil
|
||||
players.forEach { $0.start(game: game) }
|
||||
player.switchLeadership()
|
||||
// Find called player
|
||||
let ace = Card(suit, .ass)
|
||||
players.first { $0.rawCards.contains(ace) }!.switchLeadership()
|
||||
return .success
|
||||
}
|
||||
|
||||
private func performDoubleDuringGame(forPlayer player: Player) -> PlayerActionResult {
|
||||
guard phase == .playing,
|
||||
!player.isGameLeader else {
|
||||
return .tableStateInvalid
|
||||
}
|
||||
guard phase == .playing, !player.isGameLeader else {
|
||||
return .tableStateInvalid
|
||||
}
|
||||
player.numberOfRaises += 1
|
||||
players.forEach { $0.switchLeadership() }
|
||||
return .success
|
||||
@ -460,6 +525,9 @@ final class Table {
|
||||
phase = .waitingForPlayers
|
||||
gameType = nil
|
||||
minimumPlayableGame = nil
|
||||
for player in players {
|
||||
player.prepareForNewGame(isFirstPlayer: player.playsFirstCard)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -280,16 +280,27 @@ func routes(_ app: Application) throws {
|
||||
|
||||
app.post("player", "action", ":action") { req -> String in
|
||||
guard let token = req.body.string,
|
||||
let actionString = req.parameters.get("action"),
|
||||
let action = Player.Action(rawValue: actionString) else {
|
||||
let actionString = req.parameters.get("action") else {
|
||||
throw Abort(.badRequest)
|
||||
}
|
||||
switch database.performAction(playerToken: token, action: action) {
|
||||
let result: PlayerActionResult
|
||||
if let action = Player.Action(rawValue: actionString) {
|
||||
result = database.performAction(playerToken: token, action: action)
|
||||
} else if let game = GameType(rawValue: actionString) {
|
||||
result = database.select(game: game, playerToken: token)
|
||||
} else {
|
||||
throw Abort(.badRequest)
|
||||
}
|
||||
switch result {
|
||||
case .success:
|
||||
return ""
|
||||
case .invalidToken:
|
||||
throw Abort(.unauthorized) // 401
|
||||
default:
|
||||
case .noTableJoined:
|
||||
throw Abort(.preconditionFailed) // 412
|
||||
case .tableNotFull:
|
||||
throw Abort(.preconditionFailed) // 412
|
||||
case .tableStateInvalid:
|
||||
throw Abort(.preconditionFailed) // 412
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user