← poker-tutor
import Foundation
import PokerCore
import PokerAI
import PokerCoach
import PokerSession
import PokerTests
/// Sits a scripted player down for a whole assessment and prints what it concluded.
///
/// The same reason `pokerlab coach` exists: a measurement nobody can run and read for themselves
/// is a claim. This one is worth running against `--player control` as well as against a planted
/// flaw, because a coach that finds something in a player with nothing wrong is the failure that
/// matters most.
/// pokerlab assess --format cash --setup deep --hands 200
///
/// `--setup` pins every hand to one arrangement and prints how often each situation came up, which
/// is where a setup's `expected` priors come from.
func runAssessment(playerName: String, seed: UInt64, hands: Int, setupID: String? = nil,
cash: CashSetup? = nil) {
let player = plantedPlayer(named: playerName)
// The catalogue edition — and so the list of setups — follows the structure before anything
// is looked up.
if let cash { LeakCatalogue.format = cash.structure == .fixedLimit ? .limit : .cash }
if let setupID {
guard let setup = AssessmentPlanner.setup(id: setupID) else { return print("no setup \(setupID)") }
let session = playAssessment(player, seed: seed, hands: hands, pinned: setup, setup: cash)
let played = session.history.hands
print("Setup \(setup.id): \(played.count) hands, seed \(seed). How often each situation came up per hand:")
var counts: [SpotKind: Int] = [:]
for hand in played {
for verdict in hand.verdicts {
if let spot = SpotKind.recorded(verdict.spot) { counts[spot, default: 0] += 1 }
}
}
for spot in SpotKind.measured {
let rate = Double(counts[spot] ?? 0) / Double(max(1, played.count))
if rate > 0 { print(String(format: " .%@: %.2f", "\(spot)", rate)) }
}
return
}
let session = playAssessment(player, seed: seed, hands: hands, setup: cash)
guard let report = session.assessmentReport else { return print("no report") }
print("Player: \(player.name). \(report.hands) hands, seed \(seed).")
print("")
print(wrapped(report.headline))
print("")
print(wrapped(report.ratingLine))
print("")
if report.named.isEmpty, report.watching.isEmpty {
print("Nothing named.")
}
for finding in report.named {
print(" NAMED \(finding.leak.name)")
print(" \(finding.line)")
}
for finding in report.watching {
print(" WATCHING \(finding.leak.name)")
print(" \(finding.line)")
}
print("")
print("What it got to look at")
for area in report.areas {
let name = area.name.padding(toLength: 34, withPad: " ", startingAt: 0)
print(String(format: " %@ %2d/%2d sure %3.0f%%",
name, area.mistakes, area.chances, area.certainty * 100))
}
print("")
print("What it arranged")
for steer in report.steering {
print(" \(steer.line)")
}
}
private func wrapped(_ text: String, width: Int = 88) -> String {
var lines: [String] = []
var current = ""
for word in text.split(separator: " ") {
if current.count + word.count + 1 > width {
lines.append(current)
current = String(word)
} else {
current += current.isEmpty ? String(word) : " \(word)"
}
}
if !current.isEmpty { lines.append(current) }
return lines.joined(separator: "\n")
}