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.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 {
|
||||
|
@ -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 {
|
||||
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)
|
||||
}
|
||||
} else {
|
||||
// Must follow suit (called ace automatically excluded)
|
||||
followSuit()
|
||||
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) {
|
||||
|
@ -73,6 +73,10 @@ final class Table {
|
||||
players.compactMap { $0.playedCard }
|
||||
}
|
||||
|
||||
var didFinishGame: Bool {
|
||||
!players.contains { !$0.handCards.isEmpty }
|
||||
}
|
||||
|
||||
init(id: TableId, name: TableName, isPublic: Bool) {
|
||||
self.id = id
|
||||
self.name = name
|
||||
@ -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()
|
||||
}
|
||||
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:
|
||||
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
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user