Refactor player action
This commit is contained in:
parent
a2c2893499
commit
33f72c43cf
@ -13,7 +13,7 @@ private let numberOfCardsToProtectAce = 4
|
||||
let numberOfCardsPerPlayer = 8
|
||||
|
||||
|
||||
final class Player {
|
||||
final class OldPlayer {
|
||||
|
||||
let name: PlayerName
|
||||
|
||||
@ -28,9 +28,12 @@ final class Player {
|
||||
|
||||
/// The players plays/played the first card for the current trick
|
||||
var startedCurrentTrick = false
|
||||
|
||||
/// Indicate the currently highest bidder during bidding
|
||||
var isHighestBidder = false
|
||||
|
||||
/// The action available to the player
|
||||
var actions: [Action] = []
|
||||
var actions: [PlayerAction] = []
|
||||
|
||||
/// Indicates if the player doubled ("legen")
|
||||
var didDoubleAfterFourCards: Bool? = nil
|
||||
@ -111,7 +114,7 @@ final class Player {
|
||||
return true
|
||||
}
|
||||
|
||||
func canPerform(_ action: Action) -> Bool {
|
||||
func canPerform(_ action: PlayerAction) -> Bool {
|
||||
actions.contains(action)
|
||||
}
|
||||
|
||||
@ -124,6 +127,7 @@ final class Player {
|
||||
didDoubleAfterFourCards = nil
|
||||
isStillBidding = true
|
||||
isGameLeader = false
|
||||
isHighestBidder = false
|
||||
handCards = []
|
||||
playedCard = nil
|
||||
wonTricks = []
|
||||
@ -184,6 +188,7 @@ final class Player {
|
||||
|
||||
func didPerformBid() {
|
||||
isNextActor = false
|
||||
isHighestBidder = true
|
||||
actions = []
|
||||
}
|
||||
|
||||
@ -211,6 +216,7 @@ final class Player {
|
||||
func auctionEnded() {
|
||||
actions = []
|
||||
isStillBidding = false
|
||||
isHighestBidder = false
|
||||
isNextActor = false
|
||||
}
|
||||
|
||||
@ -447,13 +453,9 @@ final class Player {
|
||||
handCards[i].isPlayable = playable
|
||||
}
|
||||
}
|
||||
|
||||
func info(masked: Bool, positionInTrick: Int) -> PlayerInfo {
|
||||
.init(player: self, isMasked: masked, trickPosition: positionInTrick)
|
||||
}
|
||||
}
|
||||
|
||||
extension Player {
|
||||
extension OldPlayer {
|
||||
|
||||
/// Indicate that the player is connected when at a table
|
||||
var isConnected: Bool {
|
||||
@ -468,9 +470,9 @@ extension Player {
|
||||
}
|
||||
}
|
||||
|
||||
extension Player: Equatable {
|
||||
extension OldPlayer: Equatable {
|
||||
|
||||
static func == (lhs: Player, rhs: Player) -> Bool {
|
||||
static func == (lhs: OldPlayer, rhs: OldPlayer) -> Bool {
|
||||
lhs.name == rhs.name
|
||||
}
|
||||
}
|
@ -8,7 +8,7 @@ private extension Int {
|
||||
}
|
||||
}
|
||||
|
||||
final class Table {
|
||||
final class OldTable {
|
||||
|
||||
let id: TableId
|
||||
|
||||
@ -16,13 +16,13 @@ final class Table {
|
||||
|
||||
let isPublic: Bool
|
||||
|
||||
var players: [Player] = []
|
||||
var players: [OldPlayer] = []
|
||||
|
||||
var phase: GamePhase = .waitingForPlayers
|
||||
|
||||
var gameType: GameType? = nil
|
||||
|
||||
var minimumPlayableGame: GameType.GameClass?
|
||||
var minimumPlayableGame: GameType.GameClass = .none
|
||||
|
||||
/// Indicates if any player doubled during the current round, extending it to the next round
|
||||
var didDoubleInCurrentRound = false
|
||||
@ -53,7 +53,7 @@ final class Table {
|
||||
players.filter { $0.isStillBidding }.count
|
||||
}
|
||||
|
||||
var auctionWinner: Player {
|
||||
var auctionWinner: OldPlayer {
|
||||
players.first { $0.isStillBidding }!
|
||||
}
|
||||
|
||||
@ -93,7 +93,7 @@ final class Table {
|
||||
guard !isFull else {
|
||||
return false
|
||||
}
|
||||
let player = Player(name: player)
|
||||
let player = OldPlayer(name: player)
|
||||
players.append(player)
|
||||
if isFull {
|
||||
prepareTableForFirstGame()
|
||||
@ -107,54 +107,36 @@ final class Table {
|
||||
}
|
||||
|
||||
/// The player to play the first card of the current game
|
||||
var firstPlayer: Player {
|
||||
var firstPlayer: OldPlayer {
|
||||
players.first { $0.playsFirstCard }!
|
||||
}
|
||||
|
||||
func select(player: PlayerName) -> Player? {
|
||||
func select(player: PlayerName) -> OldPlayer? {
|
||||
players.first { $0.name == player }
|
||||
}
|
||||
|
||||
var indexOfTrickStarter: Int {
|
||||
players.firstIndex { $0.startedCurrentTrick }!
|
||||
}
|
||||
|
||||
func playerInfo(at index: Int, masked: Bool) -> PlayerInfo? {
|
||||
let starter = indexOfTrickStarter
|
||||
let layer = (4 - starter + index) % 4
|
||||
return player(at: index)?.info(masked: masked, positionInTrick: layer)
|
||||
}
|
||||
|
||||
func playerInfo(leftOf index: Int, masked: Bool) -> PlayerInfo? {
|
||||
playerInfo(at: (index + 1) % 4, masked: masked)
|
||||
}
|
||||
|
||||
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? {
|
||||
func player(at index: Int) -> OldPlayer? {
|
||||
guard index < players.count else {
|
||||
return nil
|
||||
}
|
||||
return players[index]
|
||||
}
|
||||
|
||||
func index(of player: Player) -> Int {
|
||||
func index(of player: OldPlayer) -> Int {
|
||||
players.firstIndex(of: player)!
|
||||
}
|
||||
|
||||
func nextPlayer(after player: Player) -> Player {
|
||||
func nextPlayer(after player: OldPlayer) -> OldPlayer {
|
||||
let i = index(of: player)
|
||||
let newIndex = (i + 1) % maximumPlayersPerTable
|
||||
return players[newIndex]
|
||||
}
|
||||
|
||||
func nextBidder(after player: Player) -> Player {
|
||||
func nextBidder(after player: OldPlayer) -> OldPlayer {
|
||||
// Find next player to place bid
|
||||
let index = index(of: player)
|
||||
for i in 1..<4 {
|
||||
@ -166,6 +148,11 @@ final class Table {
|
||||
}
|
||||
return player
|
||||
}
|
||||
|
||||
func availableGames(forPlayerAt index: Int) -> [GameType] {
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
func remove(player: PlayerName) {
|
||||
guard let index = players.firstIndex(where: { $0.name == player }) else {
|
||||
@ -202,7 +189,7 @@ final class Table {
|
||||
private func prepareTableForFirstGame() {
|
||||
phase = .waitingForPlayers
|
||||
gameType = nil
|
||||
minimumPlayableGame = nil // Not relevant in this phase
|
||||
minimumPlayableGame = .none // Not relevant in this phase
|
||||
didDoubleInCurrentRound = false // Not relevant in this phase
|
||||
let index = players.firstIndex { $0.playsFirstCard } ?? 0
|
||||
for i in 0..<maximumPlayersPerTable {
|
||||
@ -211,13 +198,7 @@ final class Table {
|
||||
}
|
||||
|
||||
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
|
||||
@ -285,7 +266,7 @@ final class Table {
|
||||
didDoubleInCurrentRound = false
|
||||
}
|
||||
|
||||
func perform(action: Player.Action, forPlayer player: PlayerName) -> PlayerActionResult {
|
||||
func perform(action: PlayerAction, 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)")
|
||||
@ -332,7 +313,7 @@ final class Table {
|
||||
return .success
|
||||
}
|
||||
|
||||
func perform(double: Bool, forPlayer player: Player) -> PlayerActionResult {
|
||||
func perform(double: Bool, forPlayer player: OldPlayer) -> PlayerActionResult {
|
||||
player.didDouble(double)
|
||||
guard allPlayersFinishedDoubling else {
|
||||
return .success
|
||||
@ -351,17 +332,17 @@ final class Table {
|
||||
player.assignRemainingCards(cards[index])
|
||||
}
|
||||
players.forEach { $0.startAuction() }
|
||||
minimumPlayableGame = nil
|
||||
minimumPlayableGame = .none
|
||||
phase = .bidding
|
||||
}
|
||||
|
||||
private func performWeddingCall(forPlayer player: Player) -> PlayerActionResult {
|
||||
private func performWeddingCall(forPlayer player: OldPlayer) -> 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")
|
||||
guard minimumPlayableGame.allowsWedding else {
|
||||
print("Invalid minimum game \(minimumPlayableGame) for wedding call")
|
||||
return .tableStateInvalid
|
||||
}
|
||||
guard player.canOfferWedding else {
|
||||
@ -381,7 +362,7 @@ final class Table {
|
||||
return .success
|
||||
}
|
||||
|
||||
private func performBidIncrease(forPlayer player: Player) -> PlayerActionResult {
|
||||
private func performBidIncrease(forPlayer player: OldPlayer) -> PlayerActionResult {
|
||||
guard phase == .bidding else {
|
||||
return .tableStateInvalid
|
||||
}
|
||||
@ -392,37 +373,41 @@ final class Table {
|
||||
guard player.isNextActor else {
|
||||
return .tableStateInvalid
|
||||
}
|
||||
if minimumPlayableGame == nil {
|
||||
minimumPlayableGame = .ruf
|
||||
} else {
|
||||
minimumPlayableGame!.increase()
|
||||
// TODO: Check if new player sits before old player
|
||||
// then don't increase game
|
||||
minimumPlayableGame.increase()
|
||||
if !minimumPlayableGame.allowsWedding {
|
||||
// Remove wedding offers
|
||||
players.forEach { $0.weddingOutbid() }
|
||||
}
|
||||
#warning("Fix bidding")
|
||||
// TODO: Remove highest bidder from old player
|
||||
player.didPerformBid()
|
||||
|
||||
if numberOfRemainingBidders == 1 {
|
||||
selectGame(player: player)
|
||||
return .success
|
||||
}
|
||||
// Find next player to place bid
|
||||
nextBidder(after: player).requiresBid(hasWedding: false)
|
||||
return .success
|
||||
}
|
||||
|
||||
private func handleWeddingOutbid(forPlayer player: Player) -> PlayerActionResult {
|
||||
private func handleWeddingOutbid(forPlayer player: OldPlayer) -> PlayerActionResult {
|
||||
if player.offersWedding {
|
||||
// A player offering a wedding can't outbid itself
|
||||
return .tableStateInvalid
|
||||
}
|
||||
players.forEach { $0.weddingOutbid() }
|
||||
nextBidder(after: player).requiresBid(hasWedding: true)
|
||||
nextBidder(after: player).requiresBid(hasWedding: false)
|
||||
return .success
|
||||
}
|
||||
|
||||
private func handleWeddingAccept(forPlayer player: Player) -> PlayerActionResult {
|
||||
private func handleWeddingAccept(forPlayer player: OldPlayer) -> PlayerActionResult {
|
||||
guard phase == .bidding else {
|
||||
return .tableStateInvalid
|
||||
}
|
||||
guard minimumPlayableGame == nil || minimumPlayableGame == .ruf else {
|
||||
guard minimumPlayableGame.allowsWedding else {
|
||||
return .tableStateInvalid
|
||||
}
|
||||
guard weddingOfferExists else {
|
||||
@ -450,19 +435,19 @@ final class Table {
|
||||
return .success
|
||||
}
|
||||
|
||||
private func selectedWedding(player: Player) {
|
||||
minimumPlayableGame = nil
|
||||
private func selectedWedding(player: OldPlayer) {
|
||||
minimumPlayableGame = .none
|
||||
gameType = .hochzeit
|
||||
phase = .selectWeddingCard
|
||||
players.forEach { $0.auctionEnded() }
|
||||
player.mustSelectWeddingCard()
|
||||
}
|
||||
|
||||
private func selectedCardForWedding(card: Card, player: Player) -> PlayCardResult {
|
||||
private func selectedCardForWedding(card: Card, player: OldPlayer) -> PlayCardResult {
|
||||
guard player.isNextActor,
|
||||
player.wouldAcceptWedding,
|
||||
weddingOfferExists else {
|
||||
return .invalidTableState
|
||||
return .tableStateInvalid
|
||||
}
|
||||
guard !card.isTrump(in: .hochzeit),
|
||||
player.has(card: card) else {
|
||||
@ -481,7 +466,7 @@ final class Table {
|
||||
return .success
|
||||
}
|
||||
|
||||
private func performWithdrawl(forPlayer player: Player) -> PlayerActionResult {
|
||||
private func performWithdrawl(forPlayer player: OldPlayer) -> PlayerActionResult {
|
||||
guard phase == .bidding,
|
||||
player.isNextActor,
|
||||
player.isStillBidding else {
|
||||
@ -490,7 +475,7 @@ final class Table {
|
||||
player.withdrawFromBidding()
|
||||
switch numberOfRemainingBidders {
|
||||
case 1:
|
||||
if minimumPlayableGame != nil {
|
||||
if minimumPlayableGame != .none {
|
||||
// Will only be called when at least one player placed a bid
|
||||
selectGame(player: auctionWinner)
|
||||
return .success
|
||||
@ -515,7 +500,7 @@ final class Table {
|
||||
prepareTableForFirstGame()
|
||||
}
|
||||
|
||||
private func selectGame(player: Player) {
|
||||
private func selectGame(player: OldPlayer) {
|
||||
gameType = nil
|
||||
phase = .selectGame
|
||||
players.forEach { $0.auctionEnded() }
|
||||
@ -527,14 +512,14 @@ final class Table {
|
||||
guard phase == .selectGame, player.selectsGame, game != .hochzeit else {
|
||||
return .tableStateInvalid
|
||||
}
|
||||
guard minimumPlayableGame == nil || game.gameClass >= minimumPlayableGame! else {
|
||||
guard game.gameClass >= minimumPlayableGame else {
|
||||
return .tableStateInvalid
|
||||
}
|
||||
defer { sendUpdateToAllPlayers() }
|
||||
guard let suit = game.calledSuit else {
|
||||
phase = .playing
|
||||
gameType = game
|
||||
minimumPlayableGame = nil
|
||||
minimumPlayableGame = .none
|
||||
players.forEach { $0.start(game: game) }
|
||||
player.switchLeadership()
|
||||
|
||||
@ -546,7 +531,7 @@ final class Table {
|
||||
}
|
||||
phase = .playing
|
||||
gameType = game
|
||||
minimumPlayableGame = nil
|
||||
minimumPlayableGame = .none
|
||||
players.forEach { $0.start(game: game) }
|
||||
player.switchLeadership()
|
||||
// Find called player
|
||||
@ -555,7 +540,7 @@ final class Table {
|
||||
return .success
|
||||
}
|
||||
|
||||
private func performDoubleDuringGame(forPlayer player: Player) -> PlayerActionResult {
|
||||
private func performDoubleDuringGame(forPlayer player: OldPlayer) -> PlayerActionResult {
|
||||
guard phase == .playing, !player.isGameLeader else {
|
||||
return .tableStateInvalid
|
||||
}
|
||||
@ -568,14 +553,14 @@ final class Table {
|
||||
private func reset() {
|
||||
phase = .waitingForPlayers
|
||||
gameType = nil
|
||||
minimumPlayableGame = nil
|
||||
minimumPlayableGame = .none
|
||||
for player in players {
|
||||
player.prepareForNewGame(isFirstPlayer: player.playsFirstCard)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Table {
|
||||
extension OldTable {
|
||||
|
||||
var isFull: Bool {
|
||||
players.count == maximumPlayersPerTable
|
||||
@ -588,12 +573,5 @@ extension Table {
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
@ -1,35 +1,32 @@
|
||||
import Foundation
|
||||
|
||||
extension Player {
|
||||
enum PlayerAction: String, Codable {
|
||||
/// The player can request cards to be dealt
|
||||
case deal = "deal"
|
||||
|
||||
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 doubles on the initial four cards
|
||||
case initialDoubleCost = "double"
|
||||
/// The player does not double on the initial four cards
|
||||
case noDoubleCost = "skip"
|
||||
|
||||
/// 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 offers a wedding (one trump card)
|
||||
case offerWedding = "wedding"
|
||||
/// The player can choose to accept the wedding
|
||||
case acceptWedding = "accept"
|
||||
|
||||
/// 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 matches or increases the game during auction
|
||||
case increaseOrMatchGame = "bid"
|
||||
/// The player does not want to play
|
||||
case withdrawFromAuction = "out"
|
||||
|
||||
/// 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
|
||||
}
|
||||
/// 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
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ enum PlayCardResult {
|
||||
|
||||
case noTableJoined
|
||||
|
||||
case invalidTableState
|
||||
case tableStateInvalid
|
||||
|
||||
case invalidCard
|
||||
}
|
||||
|
@ -12,4 +12,6 @@ enum PlayerActionResult {
|
||||
case tableNotFull
|
||||
|
||||
case tableStateInvalid
|
||||
|
||||
case invalidCard
|
||||
}
|
@ -284,7 +284,7 @@ func routes(_ app: Application) throws {
|
||||
throw Abort(.badRequest)
|
||||
}
|
||||
let result: PlayerActionResult
|
||||
if let action = Player.Action(rawValue: actionString) {
|
||||
if let action = PlayerAction(rawValue: actionString) {
|
||||
result = database.performAction(playerToken: token, action: action)
|
||||
} else if let game = GameType(rawValue: actionString) {
|
||||
result = database.select(game: game, playerToken: token)
|
||||
@ -302,6 +302,8 @@ func routes(_ app: Application) throws {
|
||||
throw Abort(.preconditionFailed) // 412
|
||||
case .tableStateInvalid:
|
||||
throw Abort(.preconditionFailed) // 412
|
||||
case .invalidCard:
|
||||
throw Abort(.preconditionFailed) // 412
|
||||
}
|
||||
}
|
||||
|
||||
@ -318,11 +320,12 @@ func routes(_ app: Application) throws {
|
||||
throw Abort(.unauthorized) // 401
|
||||
case .noTableJoined:
|
||||
throw Abort(.preconditionFailed) // 412
|
||||
case .invalidTableState:
|
||||
case .tableStateInvalid:
|
||||
throw Abort(.preconditionFailed) // 412
|
||||
case .invalidCard:
|
||||
throw Abort(.preconditionFailed) // 412
|
||||
|
||||
case .tableNotFull:
|
||||
throw Abort(.preconditionFailed) // 412
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user