Bug fixing
This commit is contained in:
parent
fa3aaadef8
commit
9d9c8ad71f
@ -25,7 +25,7 @@ struct TableInfo: Codable {
|
|||||||
self.playerAcross = table.playerInfo(acrossOf: playerIndex, masked: true)
|
self.playerAcross = table.playerInfo(acrossOf: playerIndex, masked: true)
|
||||||
self.playerRight = table.playerInfo(rightOf: playerIndex, masked: true)
|
self.playerRight = table.playerInfo(rightOf: playerIndex, masked: true)
|
||||||
|
|
||||||
if table.phase == .selectGame, player.selectsGame {
|
if table.phase == .bidding || table.phase == .selectGame {
|
||||||
let games = table.minimumPlayableGame?.availableGames ?? GameType.allCases
|
let games = table.minimumPlayableGame?.availableGames ?? GameType.allCases
|
||||||
self.playableGames = games.filter(player.canPlay).map { $0.id }
|
self.playableGames = games.filter(player.canPlay).map { $0.id }
|
||||||
} else {
|
} else {
|
||||||
|
@ -187,9 +187,12 @@ final class Player {
|
|||||||
actions = []
|
actions = []
|
||||||
}
|
}
|
||||||
|
|
||||||
func requiresBid() {
|
func requiresBid(hasWedding: Bool) {
|
||||||
isNextActor = true
|
isNextActor = true
|
||||||
actions = [.increaseOrMatchGame, .withdrawFromAuction]
|
actions = [.increaseOrMatchGame, .withdrawFromAuction]
|
||||||
|
if hasWedding {
|
||||||
|
actions.append(.acceptWedding)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func acceptWedding() {
|
func acceptWedding() {
|
||||||
@ -262,6 +265,7 @@ final class Player {
|
|||||||
selectsGame = false
|
selectsGame = false
|
||||||
actions = [.doubleDuringGame]
|
actions = [.doubleDuringGame]
|
||||||
isGameLeader = false
|
isGameLeader = false
|
||||||
|
handCards = game.sortingType.sort(rawCards).map { .init(card: $0, isPlayable: false) }
|
||||||
if playsFirstCard {
|
if playsFirstCard {
|
||||||
setPlayableCardsForStarter(game: game)
|
setPlayableCardsForStarter(game: game)
|
||||||
}
|
}
|
||||||
@ -282,21 +286,31 @@ final class Player {
|
|||||||
actions = []
|
actions = []
|
||||||
}
|
}
|
||||||
|
|
||||||
func didFinishTrick(canDoubleInNextRound: Bool) {
|
func didFinish(trick: Trick, winner: Bool, canDoubleInNextRound: Bool) {
|
||||||
isNextActor = false
|
isNextActor = winner
|
||||||
playedCard = nil
|
//startedCurrentTrick = winner
|
||||||
|
if winner {
|
||||||
|
wonTricks.append(trick)
|
||||||
|
}
|
||||||
if canDoubleInNextRound, !isGameLeader {
|
if canDoubleInNextRound, !isGameLeader {
|
||||||
actions = [.doubleDuringGame]
|
actions = [.doubleDuringGame]
|
||||||
} else {
|
} else {
|
||||||
actions = []
|
actions = []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
func didFinishGame() {
|
||||||
func didWin(trick: Trick) {
|
actions = [.deal]
|
||||||
self.wonTricks.append(trick)
|
|
||||||
self.isNextActor = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func clearLastTrick() {
|
||||||
|
playedCard = nil
|
||||||
|
// This flag is not set until the last trick is cleared, because
|
||||||
|
// it would mess up the stacking of the cards on the table
|
||||||
|
// which relies on this property
|
||||||
|
startedCurrentTrick = isNextActor
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
func setPlayableCards(forCurrentTrick trick: Trick, in game: GameType?) {
|
func setPlayableCards(forCurrentTrick trick: Trick, in game: GameType?) {
|
||||||
guard let game = game, isNextActor else {
|
guard let game = game, isNextActor else {
|
||||||
for i in 0..<handCards.count {
|
for i in 0..<handCards.count {
|
||||||
@ -310,15 +324,15 @@ final class Player {
|
|||||||
setAllCards(playable: true)
|
setAllCards(playable: true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard let first = trick.first else {
|
guard let firstCard = trick.first else {
|
||||||
setPlayableCardsForStarter(game: game)
|
setPlayableCardsForStarter(game: game)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let sorter = game.sortingType
|
let sorter = game.sortingType
|
||||||
|
|
||||||
guard sorter.isTrump(first) else {
|
guard sorter.isTrump(firstCard) else {
|
||||||
setPlayableCardsFollowing(suit: first.suit, game: game)
|
setPlayableCardsFollowing(suit: firstCard.suit, game: game)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard !sorter.hasTrump(in: cards) else {
|
guard !sorter.hasTrump(in: cards) else {
|
||||||
@ -326,6 +340,9 @@ final class Player {
|
|||||||
handCards = cards.map {
|
handCards = cards.map {
|
||||||
.init(card: $0, isPlayable: sorter.isTrump($0))
|
.init(card: $0, isPlayable: sorter.isTrump($0))
|
||||||
}
|
}
|
||||||
|
if !handCards.contains(where: { $0.isPlayable }) {
|
||||||
|
print("No cards to play when having to follow trump")
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Can play any card if not in calling game
|
// Can play any card if not in calling game
|
||||||
@ -338,20 +355,26 @@ final class Player {
|
|||||||
handCards = cards.map {
|
handCards = cards.map {
|
||||||
.init(card: $0, isPlayable: $0 != ace)
|
.init(card: $0, isPlayable: $0 != ace)
|
||||||
}
|
}
|
||||||
|
if !handCards.contains(where: { $0.isPlayable }) {
|
||||||
|
print("No cards to play when not having to follow trump in a called game")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setPlayableCardsFollowing(suit: Card.Suit, game: GameType) {
|
private func setPlayableCardsFollowing(suit playedSuit: Card.Suit, game: GameType) {
|
||||||
let cards = rawCards
|
let cards = rawCards
|
||||||
let sorter = game.sortingType
|
let sorter = game.sortingType
|
||||||
// No calling game, allow all cards of same suit
|
let suitCards = sorter.cards(with: playedSuit, in: cards)
|
||||||
let suitCards = sorter.cards(with: suit, in: cards)
|
|
||||||
|
|
||||||
func followSuit() {
|
func followSuit() {
|
||||||
handCards = cards.map {
|
handCards = cards.map {
|
||||||
.init(card: $0, isPlayable: !sorter.isTrump($0) && $0.suit == suit)
|
.init(card: $0, isPlayable: !sorter.isTrump($0) && $0.suit == playedSuit)
|
||||||
|
}
|
||||||
|
if !handCards.contains(where: { $0.isPlayable }) {
|
||||||
|
print("No cards to play when following suit")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
guard let called = game.calledSuit else {
|
|
||||||
|
guard let calledSuit = game.calledSuit else {
|
||||||
if suitCards.isEmpty {
|
if suitCards.isEmpty {
|
||||||
// Can play any card
|
// Can play any card
|
||||||
setAllCards(playable: true)
|
setAllCards(playable: true)
|
||||||
@ -361,20 +384,26 @@ final class Player {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let ace = Card(called, .ass)
|
print("Has called suit \(calledSuit)")
|
||||||
guard called == suit else {
|
let ace = Card(calledSuit, .ass)
|
||||||
if suitCards.isEmpty {
|
guard !suitCards.isEmpty else {
|
||||||
// Exclude called ace, all others allowed
|
// Exclude called ace, all others allowed
|
||||||
handCards = cards.map {
|
handCards = cards.map {
|
||||||
.init(card: $0, isPlayable: $0 != ace)
|
.init(card: $0, isPlayable: $0 != ace)
|
||||||
}
|
}
|
||||||
} else {
|
if !handCards.contains(where: { $0.isPlayable }) {
|
||||||
// Must follow suit (called ace automatically excluded)
|
print("No cards to play when following called suit without suit cards")
|
||||||
followSuit()
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// The called suit is player, must commit ace
|
guard calledSuit == playedSuit else {
|
||||||
|
print("Following uncalled suit since no suitable cards")
|
||||||
|
// Must follow suit (called ace not present)
|
||||||
|
followSuit()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// The called suit is played, must commit ace
|
||||||
guard cards.contains(ace) else {
|
guard cards.contains(ace) else {
|
||||||
// Must follow suit
|
// Must follow suit
|
||||||
followSuit()
|
followSuit()
|
||||||
@ -382,6 +411,9 @@ final class Player {
|
|||||||
}
|
}
|
||||||
// Must play ace
|
// Must play ace
|
||||||
handCards = cards.map { .init(card: $0, isPlayable: $0 == ace) }
|
handCards = cards.map { .init(card: $0, isPlayable: $0 == ace) }
|
||||||
|
if !handCards.contains(where: { $0.isPlayable }) {
|
||||||
|
print("No cards to play when having to play ace of called suit")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setPlayableCardsForStarter(game: GameType) {
|
private func setPlayableCardsForStarter(game: GameType) {
|
||||||
|
@ -72,6 +72,10 @@ final class Table {
|
|||||||
var currentTrick: [Card] {
|
var currentTrick: [Card] {
|
||||||
players.compactMap { $0.playedCard }
|
players.compactMap { $0.playedCard }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var didFinishGame: Bool {
|
||||||
|
!players.contains { !$0.handCards.isEmpty }
|
||||||
|
}
|
||||||
|
|
||||||
init(id: TableId, name: TableName, isPublic: Bool) {
|
init(id: TableId, name: TableName, isPublic: Bool) {
|
||||||
self.id = id
|
self.id = id
|
||||||
@ -111,10 +115,13 @@ final class Table {
|
|||||||
players.first { $0.name == player }
|
players.first { $0.name == player }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var indexOfTrickStarter: Int {
|
||||||
|
players.firstIndex { $0.startedCurrentTrick }!
|
||||||
|
}
|
||||||
|
|
||||||
func playerInfo(at index: Int, masked: Bool) -> PlayerInfo? {
|
func playerInfo(at index: Int, masked: Bool) -> PlayerInfo? {
|
||||||
let position = players.firstIndex { $0.startedCurrentTrick }!
|
let starter = indexOfTrickStarter
|
||||||
let layer = (index - position + 4) % 4
|
let layer = (4 - starter + index) % 4
|
||||||
//
|
|
||||||
return player(at: index)?.info(masked: masked, positionInTrick: layer)
|
return player(at: index)?.info(masked: masked, positionInTrick: layer)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,37 +227,61 @@ final class Table {
|
|||||||
if phase == .selectWeddingCard {
|
if phase == .selectWeddingCard {
|
||||||
return selectedCardForWedding(card: card, player: player)
|
return selectedCardForWedding(card: card, player: player)
|
||||||
}
|
}
|
||||||
guard let game = gameType,
|
guard let game = gameType, player.hasPlayable(card: card) else {
|
||||||
player.hasPlayable(card: card) else {
|
// Player only has playable cards when it is active
|
||||||
return .invalidCard
|
return .invalidCard
|
||||||
}
|
}
|
||||||
|
if hasCompletedTrick {
|
||||||
|
// Hide cards from last trick when next card is played
|
||||||
|
players.forEach { $0.clearLastTrick() }
|
||||||
|
}
|
||||||
player.play(card: card)
|
player.play(card: card)
|
||||||
if let completedTrick = completedTrick {
|
if let completedTrick = completedTrick {
|
||||||
didFinish(trick: completedTrick, in: game)
|
didFinish(trick: completedTrick, in: game)
|
||||||
|
// Update cards for empty trick
|
||||||
|
players.forEach { $0.setPlayableCards(forCurrentTrick: [], in: game) }
|
||||||
} else {
|
} else {
|
||||||
let next = nextPlayer(after: player)
|
let next = nextPlayer(after: player)
|
||||||
next.isNextActor = true
|
next.isNextActor = true
|
||||||
player.isNextActor = false
|
player.isNextActor = false
|
||||||
|
// Update cards for empty trick
|
||||||
|
players.forEach { $0.setPlayableCards(forCurrentTrick: currentTrick, in: game) }
|
||||||
|
}
|
||||||
|
if didFinishGame {
|
||||||
|
finishedGame()
|
||||||
}
|
}
|
||||||
updatePlayableCards()
|
|
||||||
sendUpdateToAllPlayers()
|
sendUpdateToAllPlayers()
|
||||||
return .success
|
return .success
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updatePlayableCards() {
|
private func finishedGame() {
|
||||||
let playedCards = currentTrick
|
phase = .gameFinished
|
||||||
players.forEach {
|
players.forEach { $0.didFinishGame() }
|
||||||
$0.setPlayableCards(forCurrentTrick: playedCards, in: gameType)
|
guard didFinishGame else {
|
||||||
|
// Either no doubles or bids
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
// TODO: Calculate winner, points, cost
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func didFinish(trick: Trick, in game: GameType) {
|
func didFinish(trick: Trick, in game: GameType) {
|
||||||
// If trick is completed, calculate winner
|
// If trick is completed, calculate winner
|
||||||
let index = trick.highCardIndex(forGame: game)
|
let startIndex = indexOfTrickStarter
|
||||||
|
let rotated = trick.rotated(toStartAt: startIndex)
|
||||||
|
let index = rotated.highCardIndex(forGame: game)
|
||||||
|
print("Winner \(index) for \(rotated)")
|
||||||
|
let winner = players[(startIndex + index) % 4]
|
||||||
players.forEach {
|
players.forEach {
|
||||||
$0.didFinishTrick(canDoubleInNextRound: didDoubleInCurrentRound)
|
$0.didFinish(trick: trick,
|
||||||
|
winner: winner == $0,
|
||||||
|
canDoubleInNextRound: didDoubleInCurrentRound)
|
||||||
|
}
|
||||||
|
if game == .bettel && winner.isGameLeader {
|
||||||
|
// A bettel is lost if a single trick is won by the leader
|
||||||
|
finishedGame()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
players[index].didWin(trick: trick)
|
|
||||||
didDoubleInCurrentRound = false
|
didDoubleInCurrentRound = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -285,9 +316,12 @@ final class Table {
|
|||||||
guard isFull else {
|
guard isFull else {
|
||||||
return .tableNotFull
|
return .tableNotFull
|
||||||
}
|
}
|
||||||
guard phase == .waitingForPlayers else {
|
guard phase == .waitingForPlayers || phase == .gameFinished else {
|
||||||
return .tableStateInvalid
|
return .tableStateInvalid
|
||||||
}
|
}
|
||||||
|
if phase == .gameFinished {
|
||||||
|
prepareForNextGame()
|
||||||
|
}
|
||||||
|
|
||||||
let cards = Dealer.dealFirstCards()
|
let cards = Dealer.dealFirstCards()
|
||||||
for (index, player) in players.enumerated() {
|
for (index, player) in players.enumerated() {
|
||||||
@ -303,10 +337,11 @@ final class Table {
|
|||||||
guard allPlayersFinishedDoubling else {
|
guard allPlayersFinishedDoubling else {
|
||||||
return .success
|
return .success
|
||||||
}
|
}
|
||||||
guard initialDoubleExists else {
|
if initialDoubleExists {
|
||||||
return dealNextGame()
|
dealAdditionalCards()
|
||||||
|
} else {
|
||||||
|
finishedGame()
|
||||||
}
|
}
|
||||||
dealAdditionalCards()
|
|
||||||
return .success
|
return .success
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -329,7 +364,7 @@ final class Table {
|
|||||||
print("Invalid minimum game \(minimumPlayableGame!) for wedding call")
|
print("Invalid minimum game \(minimumPlayableGame!) for wedding call")
|
||||||
return .tableStateInvalid
|
return .tableStateInvalid
|
||||||
}
|
}
|
||||||
guard player.offersWedding else {
|
guard player.canOfferWedding else {
|
||||||
print("Player does not offer wedding")
|
print("Player does not offer wedding")
|
||||||
return .tableStateInvalid
|
return .tableStateInvalid
|
||||||
}
|
}
|
||||||
@ -365,8 +400,11 @@ final class Table {
|
|||||||
players.forEach { $0.weddingOutbid() }
|
players.forEach { $0.weddingOutbid() }
|
||||||
}
|
}
|
||||||
player.didPerformBid()
|
player.didPerformBid()
|
||||||
|
if numberOfRemainingBidders == 1 {
|
||||||
|
selectGame(player: player)
|
||||||
|
}
|
||||||
// Find next player to place bid
|
// Find next player to place bid
|
||||||
nextBidder(after: player).requiresBid()
|
nextBidder(after: player).requiresBid(hasWedding: false)
|
||||||
return .success
|
return .success
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -376,7 +414,7 @@ final class Table {
|
|||||||
return .tableStateInvalid
|
return .tableStateInvalid
|
||||||
}
|
}
|
||||||
players.forEach { $0.weddingOutbid() }
|
players.forEach { $0.weddingOutbid() }
|
||||||
firstPlayer.requiresBid()
|
nextBidder(after: player).requiresBid(hasWedding: true)
|
||||||
return .success
|
return .success
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -408,7 +446,7 @@ final class Table {
|
|||||||
$0.weddingAccepted()
|
$0.weddingAccepted()
|
||||||
}
|
}
|
||||||
player.acceptWedding()
|
player.acceptWedding()
|
||||||
nextBidder(after: player).requiresBid()
|
nextBidder(after: player).requiresBid(hasWedding: false)
|
||||||
return .success
|
return .success
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -452,27 +490,32 @@ final class Table {
|
|||||||
player.withdrawFromBidding()
|
player.withdrawFromBidding()
|
||||||
switch numberOfRemainingBidders {
|
switch numberOfRemainingBidders {
|
||||||
case 1:
|
case 1:
|
||||||
selectGame(player: auctionWinner)
|
if minimumPlayableGame != nil {
|
||||||
|
// Will only be called when at least one player placed a bid
|
||||||
|
selectGame(player: auctionWinner)
|
||||||
|
return .success
|
||||||
|
}
|
||||||
case 0:
|
case 0:
|
||||||
// All players withdrawn, deal new cards
|
// All players withdrawn, deal new cards
|
||||||
return dealNextGame()
|
finishedGame()
|
||||||
|
return .success
|
||||||
default:
|
default:
|
||||||
nextBidder(after: player).requiresBid()
|
break
|
||||||
}
|
}
|
||||||
|
nextBidder(after: player).requiresBid(hasWedding: weddingOfferExists)
|
||||||
return .success
|
return .success
|
||||||
}
|
}
|
||||||
|
|
||||||
private func dealNextGame() -> PlayerActionResult {
|
private func prepareForNextGame() {
|
||||||
let first = firstPlayer
|
let first = firstPlayer
|
||||||
let newPlayer = self.nextBidder(after: first)
|
let newPlayer = self.nextBidder(after: first)
|
||||||
first.playsFirstCard = false
|
first.playsFirstCard = false
|
||||||
newPlayer.playsFirstCard = true
|
newPlayer.playsFirstCard = true
|
||||||
|
print("Made \(newPlayer.name) to new starter")
|
||||||
prepareTableForFirstGame()
|
prepareTableForFirstGame()
|
||||||
return dealInitialCards()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func selectGame(player: Player) {
|
private func selectGame(player: Player) {
|
||||||
minimumPlayableGame = nil
|
|
||||||
gameType = nil
|
gameType = nil
|
||||||
phase = .selectGame
|
phase = .selectGame
|
||||||
players.forEach { $0.auctionEnded() }
|
players.forEach { $0.auctionEnded() }
|
||||||
@ -518,6 +561,7 @@ final class Table {
|
|||||||
}
|
}
|
||||||
player.numberOfRaises += 1
|
player.numberOfRaises += 1
|
||||||
players.forEach { $0.switchLeadership() }
|
players.forEach { $0.switchLeadership() }
|
||||||
|
didDoubleInCurrentRound = true
|
||||||
return .success
|
return .success
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user