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.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
self.playableGames = games.filter(player.canPlay).map { $0.id }
} else {

View File

@ -187,9 +187,12 @@ final class Player {
actions = []
}
func requiresBid() {
func requiresBid(hasWedding: Bool) {
isNextActor = true
actions = [.increaseOrMatchGame, .withdrawFromAuction]
if hasWedding {
actions.append(.acceptWedding)
}
}
func acceptWedding() {
@ -262,6 +265,7 @@ final class Player {
selectsGame = false
actions = [.doubleDuringGame]
isGameLeader = false
handCards = game.sortingType.sort(rawCards).map { .init(card: $0, isPlayable: false) }
if playsFirstCard {
setPlayableCardsForStarter(game: game)
}
@ -282,21 +286,31 @@ final class Player {
actions = []
}
func didFinishTrick(canDoubleInNextRound: Bool) {
isNextActor = false
playedCard = nil
func didFinish(trick: Trick, winner: Bool, canDoubleInNextRound: Bool) {
isNextActor = winner
//startedCurrentTrick = winner
if winner {
wonTricks.append(trick)
}
if canDoubleInNextRound, !isGameLeader {
actions = [.doubleDuringGame]
} else {
actions = []
}
}
func didWin(trick: Trick) {
self.wonTricks.append(trick)
self.isNextActor = true
func didFinishGame() {
actions = [.deal]
}
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?) {
guard let game = game, isNextActor else {
for i in 0..<handCards.count {
@ -310,15 +324,15 @@ final class Player {
setAllCards(playable: true)
return
}
guard let first = trick.first else {
guard let firstCard = trick.first else {
setPlayableCardsForStarter(game: game)
return
}
let sorter = game.sortingType
guard sorter.isTrump(first) else {
setPlayableCardsFollowing(suit: first.suit, game: game)
guard sorter.isTrump(firstCard) else {
setPlayableCardsFollowing(suit: firstCard.suit, game: game)
return
}
guard !sorter.hasTrump(in: cards) else {
@ -326,6 +340,9 @@ final class Player {
handCards = cards.map {
.init(card: $0, isPlayable: sorter.isTrump($0))
}
if !handCards.contains(where: { $0.isPlayable }) {
print("No cards to play when having to follow trump")
}
return
}
// Can play any card if not in calling game
@ -338,20 +355,26 @@ final class Player {
handCards = cards.map {
.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 sorter = game.sortingType
// No calling game, allow all cards of same suit
let suitCards = sorter.cards(with: suit, in: cards)
let suitCards = sorter.cards(with: playedSuit, in: cards)
func followSuit() {
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 {
// Can play any card
setAllCards(playable: true)
@ -361,20 +384,26 @@ final class Player {
}
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()
print("Has called suit \(calledSuit)")
let ace = Card(calledSuit, .ass)
guard !suitCards.isEmpty else {
// Exclude called ace, all others allowed
handCards = cards.map {
.init(card: $0, isPlayable: $0 != ace)
}
if !handCards.contains(where: { $0.isPlayable }) {
print("No cards to play when following called suit without suit cards")
}
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 {
// Must follow suit
followSuit()
@ -382,6 +411,9 @@ final class Player {
}
// Must play 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) {

View File

@ -72,6 +72,10 @@ final class Table {
var currentTrick: [Card] {
players.compactMap { $0.playedCard }
}
var didFinishGame: Bool {
!players.contains { !$0.handCards.isEmpty }
}
init(id: TableId, name: TableName, isPublic: Bool) {
self.id = id
@ -111,10 +115,13 @@ final class Table {
players.first { $0.name == player }
}
var indexOfTrickStarter: Int {
players.firstIndex { $0.startedCurrentTrick }!
}
func playerInfo(at index: Int, masked: Bool) -> PlayerInfo? {
let position = players.firstIndex { $0.startedCurrentTrick }!
let layer = (index - position + 4) % 4
//
let starter = indexOfTrickStarter
let layer = (4 - starter + index) % 4
return player(at: index)?.info(masked: masked, positionInTrick: layer)
}
@ -220,37 +227,61 @@ final class Table {
if phase == .selectWeddingCard {
return selectedCardForWedding(card: card, player: player)
}
guard let game = gameType,
player.hasPlayable(card: card) else {
guard let game = gameType, player.hasPlayable(card: card) else {
// Player only has playable cards when it is active
return .invalidCard
}
if hasCompletedTrick {
// Hide cards from last trick when next card is played
players.forEach { $0.clearLastTrick() }
}
player.play(card: card)
if let completedTrick = completedTrick {
didFinish(trick: completedTrick, in: game)
// Update cards for empty trick
players.forEach { $0.setPlayableCards(forCurrentTrick: [], in: game) }
} else {
let next = nextPlayer(after: player)
next.isNextActor = true
player.isNextActor = false
// Update cards for empty trick
players.forEach { $0.setPlayableCards(forCurrentTrick: currentTrick, in: game) }
}
if didFinishGame {
finishedGame()
}
updatePlayableCards()
sendUpdateToAllPlayers()
return .success
}
private func updatePlayableCards() {
let playedCards = currentTrick
players.forEach {
$0.setPlayableCards(forCurrentTrick: playedCards, in: gameType)
private func finishedGame() {
phase = .gameFinished
players.forEach { $0.didFinishGame() }
guard didFinishGame else {
// Either no doubles or bids
return
}
// TODO: Calculate winner, points, cost
}
func didFinish(trick: Trick, in game: GameType) {
// 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 {
$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
}
@ -285,9 +316,12 @@ final class Table {
guard isFull else {
return .tableNotFull
}
guard phase == .waitingForPlayers else {
guard phase == .waitingForPlayers || phase == .gameFinished else {
return .tableStateInvalid
}
if phase == .gameFinished {
prepareForNextGame()
}
let cards = Dealer.dealFirstCards()
for (index, player) in players.enumerated() {
@ -303,10 +337,11 @@ final class Table {
guard allPlayersFinishedDoubling else {
return .success
}
guard initialDoubleExists else {
return dealNextGame()
if initialDoubleExists {
dealAdditionalCards()
} else {
finishedGame()
}
dealAdditionalCards()
return .success
}
@ -329,7 +364,7 @@ final class Table {
print("Invalid minimum game \(minimumPlayableGame!) for wedding call")
return .tableStateInvalid
}
guard player.offersWedding else {
guard player.canOfferWedding else {
print("Player does not offer wedding")
return .tableStateInvalid
}
@ -365,8 +400,11 @@ final class Table {
players.forEach { $0.weddingOutbid() }
}
player.didPerformBid()
if numberOfRemainingBidders == 1 {
selectGame(player: player)
}
// Find next player to place bid
nextBidder(after: player).requiresBid()
nextBidder(after: player).requiresBid(hasWedding: false)
return .success
}
@ -376,7 +414,7 @@ final class Table {
return .tableStateInvalid
}
players.forEach { $0.weddingOutbid() }
firstPlayer.requiresBid()
nextBidder(after: player).requiresBid(hasWedding: true)
return .success
}
@ -408,7 +446,7 @@ final class Table {
$0.weddingAccepted()
}
player.acceptWedding()
nextBidder(after: player).requiresBid()
nextBidder(after: player).requiresBid(hasWedding: false)
return .success
}
@ -452,27 +490,32 @@ final class Table {
player.withdrawFromBidding()
switch numberOfRemainingBidders {
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:
// All players withdrawn, deal new cards
return dealNextGame()
finishedGame()
return .success
default:
nextBidder(after: player).requiresBid()
break
}
nextBidder(after: player).requiresBid(hasWedding: weddingOfferExists)
return .success
}
private func dealNextGame() -> PlayerActionResult {
private func prepareForNextGame() {
let first = firstPlayer
let newPlayer = self.nextBidder(after: first)
first.playsFirstCard = false
newPlayer.playsFirstCard = true
print("Made \(newPlayer.name) to new starter")
prepareTableForFirstGame()
return dealInitialCards()
}
private func selectGame(player: Player) {
minimumPlayableGame = nil
gameType = nil
phase = .selectGame
players.forEach { $0.auctionEnded() }
@ -518,6 +561,7 @@ final class Table {
}
player.numberOfRaises += 1
players.forEach { $0.switchLeadership() }
didDoubleInCurrentRound = true
return .success
}