Show proper game summary

This commit is contained in:
Christoph Hagen 2021-12-21 14:24:53 +01:00
parent b5cc395456
commit 7de2352c61
8 changed files with 192 additions and 78 deletions

View File

@ -2,38 +2,84 @@ import Foundation
struct EnglishGameSummarizer: GameSummarizer { struct EnglishGameSummarizer: GameSummarizer {
let game: GameSummary let table: FinishedTable
private var winText: String { private var winText: String {
game.didWin ? "won" : "lost" table.selectorDidWin ? "won" : "lost"
} }
private var gameText: String { private var gameText: String {
"game" switch table.game {
case .rufBlatt:
return "the call of Blatt"
case .rufEichel:
return "the call of Eichel"
case .rufSchelln:
return "the call of Schelln"
case .bettel:
return "the Bettel"
case .geier:
return "the Geier"
case .wenz:
return "the Wenz"
case .hochzeit:
return "the wedding"
case .soloBlatt:
return "the Solo Blatt"
case .soloEichel:
return "the Solo Eichel"
case .soloHerz:
return "the Solo Herz"
case .soloSchelln:
return "the Solo Schelln"
}
} }
private var coPlayerNames: String { private var coPlayerNames: String {
switch game.coPlayers.count { let coPlayers = table.coPlayers
switch coPlayers.count {
case 0: case 0:
return "" return ""
case 1: case 1:
return " with \(game.coPlayers[0])" return " with \(coPlayers[0].name)"
case 2: case 2:
return " with \(game.coPlayers[0]) and \(game.coPlayers[1])" return " with \(coPlayers[0].name) and \(coPlayers[1].name)"
default: default:
return "" return ""
} }
} }
private var costText: String { private var costText: String {
guard game.cost >= 100 else { let cost = table.cost
return "\(game.cost) cents" guard cost >= 100 else {
return "\(cost) cents"
} }
return String(format: "%d.%02d €", game.cost / 100, game.cost % 100) return String(format: "%d.%02d €", cost / 100, cost % 100)
}
private var costExplanation: String {
var components = [String]()
components.append("Game \(table.game.basicCost)")
if !table.isBettel {
if table.isSchwarz {
components.append("Schwarz")
} else if table.isSchneider {
components.append("Schneider")
}
if table.leadingTrumps > 0 {
components.append("\(table.leadingTrumps) Laufende")
}
}
components.append("\(table.totalNumberOfDoubles)x doubled")
return components.joined(separator: ", ")
} }
var text: String { var text: String {
"\(game.leader) \(winText) a \(gameText)\(coPlayerNames) collecting \(game.leaderPoints) points. " + let start = "\(table.gameSelector.name) \(winText) the \(gameText)"
"The game cost \(costText)." let cost = " The game costs \(costText) (\(costExplanation))."
guard table.game != .bettel else {
return start + cost
}
return start + "\(coPlayerNames) with \(table.selectorTeamPoints) points." + cost
} }
} }

View File

@ -2,7 +2,9 @@ import Foundation
protocol GameSummarizer { protocol GameSummarizer {
init(game: GameSummary) var table: FinishedTable { get }
init(table: FinishedTable)
var text: String { get } var text: String { get }
} }

View File

@ -2,14 +2,14 @@ import Foundation
struct GermanGameSummarizer: GameSummarizer { struct GermanGameSummarizer: GameSummarizer {
let game: GameSummary let table: FinishedTable
var winText: String { private var winText: String {
game.didWin ? "gewinnt" : "verliert" table.selectorDidWin ? "gewinnt" : "verliert"
} }
var gameText: String { private var gameText: String {
switch GameType(id: game.game)! { switch table.game {
case .rufBlatt: case .rufBlatt:
return "den Ruf Blatt" return "den Ruf Blatt"
case .rufEichel: case .rufEichel:
@ -35,28 +35,51 @@ struct GermanGameSummarizer: GameSummarizer {
} }
} }
var coPlayerNames: String { private var coPlayerNames: String {
switch game.coPlayers.count { let coPlayers = table.coPlayers
switch coPlayers.count {
case 0: case 0:
return "" return ""
case 1: case 1:
return " mit \(game.coPlayers[0])" return " mit \(coPlayers[0].name)"
case 2: case 2:
return " mit \(game.coPlayers[0]) und \(game.coPlayers[1])" return " mit \(coPlayers[0].name) und \(coPlayers[1].name)"
default: default:
return "" return ""
} }
} }
var costText: String { private var costText: String {
guard game.cost >= 100 else { let cost = table.cost
return "\(game.cost) Cent" guard cost >= 100 else {
return "\(cost) Cent"
} }
return String(format: "%d.%02d €", game.cost / 100, game.cost % 100) return String(format: "%d.%02d €", cost / 100, cost % 100)
}
private var costExplanation: String {
var components = [String]()
components.append("Grundspiel \(table.game.basicCost)")
if !table.isBettel {
if table.isSchwarz {
components.append("Schwarz")
} else if table.isSchneider {
components.append("Schneider")
}
if table.leadingTrumps > 0 {
components.append("\(table.leadingTrumps) Laufende")
}
}
components.append("\(table.totalNumberOfDoubles)x gedoppelt")
return components.joined(separator: ", ")
} }
var text: String { var text: String {
"\(game.leader) \(winText) \(gameText)\(coPlayerNames) mit \(game.leaderPoints) Punkten. " + let start = "\(table.gameSelector.name) \(winText) \(gameText)"
"Das Spiel kostet \(costText)." let cost = " Das Spiel kostet \(costText) (\(costExplanation))."
guard table.game != .bettel else {
return start + cost
}
return start + "\(coPlayerNames) mit \(table.selectorTeamPoints) Punkten." + cost
} }
} }

View File

@ -2,7 +2,7 @@ import Foundation
struct GameSummary: Codable, Equatable { struct GameSummary: Codable, Equatable {
let leader: PlayerName let selector: PlayerName
let coPlayers: [PlayerName] let coPlayers: [PlayerName]
@ -10,24 +10,20 @@ struct GameSummary: Codable, Equatable {
let game: GameId let game: GameId
let leaderPoints: Int let points: Int
let cost: Int let cost: Int
var text: String = "" var text: String = ""
init(table: FinishedTable, language: SupportedLanguage) { init(table: FinishedTable, language: SupportedLanguage) {
let leader = table.players.first { $0.selectedGame }! self.selector = table.gameSelector.name
self.coPlayers = table.players self.coPlayers = table.coPlayers.map { $0.name }
.filter { $0 != leader && $0.leadsGame == leader.leadsGame }
.map { $0.name }
self.leader = leader.name
self.game = table.game.id self.game = table.game.id
self.leaderPoints = table.leadingPoints self.points = table.selectorTeamPoints
self.didWin = table.winners.contains(player: leader.name) self.didWin = table.selectorDidWin
self.cost = table.game.basicCost self.cost = table.cost
// TODO: Calculate cost correctly self.text = language.gameSummarizer.init(table: table).text
self.text = language.gameSummarizer.init(game: self).text
} }
} }

View File

@ -19,7 +19,7 @@ final class PlayingPlayer: CardHoldingPlayer {
var leadsGame: Bool var leadsGame: Bool
var numberOfDoubles = 0 var numberOfRaises = 0
/// All tricks won by the player in this game /// All tricks won by the player in this game
var wonTricks: [Trick] = [] var wonTricks: [Trick] = []
@ -34,8 +34,13 @@ final class PlayingPlayer: CardHoldingPlayer {
} }
} }
/// The players has been called in a call game
var isCallee: Bool {
isCalledWithAce != nil
}
private var isUnknownCallee: Bool { private var isUnknownCallee: Bool {
isCalledWithAce != nil && !didPlayCalledAce isCallee && !didPlayCalledAce
} }
override var actions: [PlayerAction] { override var actions: [PlayerAction] {
@ -165,7 +170,7 @@ final class PlayingPlayer: CardHoldingPlayer {
if leadsGame { if leadsGame {
states.append(.leadsGame) states.append(.leadsGame)
} }
if numberOfDoubles > 0 { if numberOfRaises > 0 {
states.append(.didRaise) states.append(.didRaise)
} }
return states return states

View File

@ -9,7 +9,6 @@ final class DealingTable: AbstractTable<DealingPlayer> {
player.cards = cards[index] player.cards = cards[index]
} }
super.init(table: table, players: players) super.init(table: table, players: players)
print("\(self.players[0].cards.count) cards")
} }
/// All players either doubled or didn't double /// All players either doubled or didn't double

View File

@ -4,42 +4,60 @@ final class FinishedTable: AbstractTable<FinishedPlayer> {
let game: GameType let game: GameType
let totalNumberOfDoubles: Int
let leadingTrumps: Int
var cost: Int {
guard !isBettel else {
return game.basicCost * 2^^totalNumberOfDoubles
}
var cost = game.basicCost
if isSchwarz {
cost += 10
} else if isSchneider {
cost += 5
}
cost += 5 * leadingTrumps
return cost * 2^^totalNumberOfDoubles
}
var gameSelector: FinishedPlayer {
players.first { $0.selectedGame }!
}
let selectorDidWin: Bool
var coPlayers: [FinishedPlayer] {
let selector = gameSelector
return players.filter { $0 != selector && $0.leadsGame == selector.leadsGame }
}
var winners: [FinishedPlayer] { var winners: [FinishedPlayer] {
leadersHaveWon ? leaders : opponents let selectorLeads = gameSelector.leadsGame
return players.filter { $0.leadsGame == (selectorDidWin == selectorLeads) }
} }
var loosers: [FinishedPlayer] { var selectorTeamPoints: Int {
leadersHaveWon ? opponents : leaders gameSelector.points + coPlayers.map { $0.points }.reduce(0, +)
} }
var leaders: [FinishedPlayer] { var isBettel: Bool {
players.filter { $0.leadsGame } game == .bettel
}
var opponents: [FinishedPlayer] {
players.filter { !$0.leadsGame }
}
var winningPoints: Int {
leadersHaveWon ? leadingPoints : 120 - leadingPoints
}
var loosingPoints: Int {
leadersHaveWon ? 120 - leadingPoints : leadingPoints
}
let leadingPoints: Int
var leadersHaveWon: Bool {
leadingPoints > 60
} }
var isSchwarz: Bool { var isSchwarz: Bool {
loosingPoints == 0 !isBettel && (selectorTeamPoints == 0 || selectorTeamPoints == 120)
} }
var isSchneider: Bool { var isSchneider: Bool {
loosingPoints < (leadersHaveWon ? 30 : 31) guard !isBettel else {
return false
}
let points = selectorTeamPoints
let leads = gameSelector.leadsGame
let limit = leads ? 31 : 30
return points < limit || 120 - points < limit
} }
override var playedGame: GameType? { override var playedGame: GameType? {
@ -47,16 +65,29 @@ final class FinishedTable: AbstractTable<FinishedPlayer> {
} }
init(table: PlayingTable) { init(table: PlayingTable) {
let players = table.players.map(FinishedPlayer.init) let players = table.players.map(FinishedPlayer.init)
let selector = table.players.first { $0.selectsGame }!
self.game = table.game self.game = table.game
leadingPoints = players self.totalNumberOfDoubles = table.totalNumberOfDoubles
.filter { $0.leadsGame } defer {
for player in winners {
player.isNextActor = true
}
}
guard table.game != .bettel else {
self.selectorDidWin = selector.wonTricks.isEmpty
self.leadingTrumps = 0
super.init(table: table, players: players)
return
}
let selectorLeads = selector.leadsGame
let teamPoints = players
.filter { $0.leadsGame == selectorLeads }
.map { $0.points } .map { $0.points }
.reduce(0, +) .reduce(0, +)
// TODO: Set isNextActor for winners self.selectorDidWin = teamPoints > (selectorLeads ? 60 : 59)
// TODO: Check for bettel self.leadingTrumps = table.leadingTrumps
// TODO: Set schneider, schwarz, cost
super.init(table: table, players: players) super.init(table: table, players: players)
} }

View File

@ -4,6 +4,8 @@ final class PlayingTable: AbstractTable<PlayingPlayer> {
let game: GameType let game: GameType
let leadingTrumps: Int
var indexOfTrickStarter = 0 var indexOfTrickStarter = 0
var didDoubleInCurrentRound = false var didDoubleInCurrentRound = false
@ -32,6 +34,12 @@ final class PlayingTable: AbstractTable<PlayingPlayer> {
!players.contains { !$0.cards.isEmpty } !players.contains { !$0.cards.isEmpty }
} }
var totalNumberOfDoubles: Int {
players.map {
$0.numberOfRaises + ($0.didDouble ? 1 : 0)
}.reduce(0,+)
}
override var playedGame: GameType? { override var playedGame: GameType? {
game game
} }
@ -53,6 +61,11 @@ final class PlayingTable: AbstractTable<PlayingPlayer> {
private init(table: ManageableTable, players: [PlayingPlayer], game: GameType) { private init(table: ManageableTable, players: [PlayingPlayer], game: GameType) {
self.game = game self.game = game
let selectorCards = players.filter { $0.leadsGame || $0.isCallee }.map { $0.cards }.reduce([], +)
let otherCards = players.filter { !$0.leadsGame && !$0.isCallee }.map { $0.cards }.reduce([], +)
let selectorTrumps = game.sortingType.consecutiveTrumps(selectorCards)
let otherTrumps = game.sortingType.consecutiveTrumps(otherCards)
self.leadingTrumps = max(selectorTrumps, otherTrumps)
super.init(table: table, players: players) super.init(table: table, players: players)
players.forEach { $0.sortCards(for: game) } players.forEach { $0.sortCards(for: game) }
players.first!.isNextActor = true players.first!.isNextActor = true
@ -75,7 +88,7 @@ final class PlayingTable: AbstractTable<PlayingPlayer> {
print("Player \(name) is not allowed to raise") print("Player \(name) is not allowed to raise")
return (.tableStateInvalid, nil) return (.tableStateInvalid, nil)
} }
player.numberOfDoubles += 1 player.numberOfRaises += 1
players.forEach { $0.switchLead() } players.forEach { $0.switchLead() }
self.didDoubleInCurrentRound = true self.didDoubleInCurrentRound = true
return (.success, nil) return (.success, nil)
@ -122,8 +135,8 @@ final class PlayingTable: AbstractTable<PlayingPlayer> {
winner.wonTricks.append(trick) winner.wonTricks.append(trick)
winner.isNextActor = true winner.isNextActor = true
if game == .bettel && winner.leadsGame { if game == .bettel && winner.selectsGame {
// A bettel is lost if a single trick is won by the leader // A bettel is lost if a single trick is won by the game selector
return finishedGame() return finishedGame()
} }
@ -136,7 +149,6 @@ final class PlayingTable: AbstractTable<PlayingPlayer> {
private func finishedGame() -> (result: PlayerActionResult, table: ManageableTable?) { private func finishedGame() -> (result: PlayerActionResult, table: ManageableTable?) {
let table = FinishedTable(table: self) let table = FinishedTable(table: self)
print("\(table.winners) have won with \(table.winningPoints) to \(table.loosingPoints) points")
return (.success, table) return (.success, table)
} }
} }