Bug fixing

This commit is contained in:
Christoph Hagen 2021-12-07 09:09:51 +01:00
parent fa3aaadef8
commit 9d9c8ad71f
3 changed files with 131 additions and 55 deletions

View File

@ -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 {

View File

@ -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) {

View File

@ -73,6 +73,10 @@ final class Table {
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
self.name = name self.name = name
@ -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() dealAdditionalCards()
} else {
finishedGame()
}
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:
if minimumPlayableGame != nil {
// Will only be called when at least one player placed a bid
selectGame(player: auctionWinner) 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
} }