← poker-tutor
import Foundation
import PokerCoach
/// Which of the products this build is.
///
/// One codebase, two apps per game: the tournament coach and the cash coach. Everything the two
/// differ in — the name on the screen, the bundle id, where the history lives, the keys the
/// settings are kept under, the subscription sold — is written down here once and read from
/// here, never from a bundle. The app shell chooses ONE of these before anything else runs; the
/// suite and the command line get the tournament one unless a test says otherwise.
public struct AppVariant: Sendable, Equatable {
public enum Format: String, Sendable, CaseIterable {
/// Blinds climb, nine players, three paid.
case tournament
/// Blinds stay put, a hundred big blinds to sit down with, cash out when you like.
case cash
}
public let format: Format
/// The name on Home and on the phone.
public let displayName: String
public let bundleID: String
/// The Application Support folder the history lives in. Two apps, two folders, two histories.
public let historyFolder: String
/// Every UserDefaults key starts with this. On iOS each app has its own defaults anyway; on
/// a Mac two apps that shared a prefix would share a tutorial-seen flag.
public let defaultsPrefix: String
/// The one product sold. Permanent once a build carrying it has been uploaded.
public let storeProductID: String
/// The key the free-hands tally lives under. The tournament app's is the unprefixed name it
/// shipped with, so nobody's count resets.
public let tallyKey: String
/// The last part of this product's page on thedigitaladlab.com. The About link in Settings
/// and the privacy link on the paywall are built from it, so the cash app points at its own
/// pages rather than the tournament app's — Apple checks the privacy URL against the listing.
public let webSlug: String
public var pageURL: String { "https://www.thedigitaladlab.com/\(webSlug)" }
public var privacyURL: String { "https://www.thedigitaladlab.com/\(webSlug)-privacy" }
public init(format: Format, displayName: String, bundleID: String, historyFolder: String,
defaultsPrefix: String, storeProductID: String, tallyKey: String, webSlug: String) {
self.format = format
self.displayName = displayName
self.bundleID = bundleID
self.historyFolder = historyFolder
self.defaultsPrefix = defaultsPrefix
self.storeProductID = storeProductID
self.tallyKey = tallyKey
self.webSlug = webSlug
}
/// The tournament coach. Every string here is the one the app shipped with on Ken's phone.
public static let pokerTutor = AppVariant(
format: .tournament,
displayName: "Poker Tutor",
bundleID: "com.thedigitaladlab.pokertutor",
historyFolder: "PokerTutor",
defaultsPrefix: "PokerTutor.",
storeProductID: "com.thedigitaladlab.pokertutor.yearly",
tallyKey: "trial.handsPlayed",
webSlug: "poker-tutor"
)
/// The cash coach.
public static let pokerTutorCash = AppVariant(
format: .cash,
displayName: "Poker Tutor Cash",
bundleID: "com.thedigitaladlab.pokertutor.cash",
historyFolder: "PokerTutorCash",
defaultsPrefix: "PokerTutorCash.",
storeProductID: "com.thedigitaladlab.pokertutor.cash.yearly",
tallyKey: "PokerTutorCash.trial.handsPlayed",
webSlug: "poker-tutor-cash"
)
/// The product this process is. Set once by the app shell, before the first screen or the
/// first file read; left at the tournament product by the suite and the command line.
nonisolated(unsafe) public static var current: AppVariant = .pokerTutor
/// Makes this the product the process is, and tells the coach which game it is judging.
///
/// Two settings, one call: the identity strings live here and the catalogue edition lives in
/// `PokerCoach`, which cannot see this type, so they would drift apart if they were set
/// separately. Every `TableSession` calls this with its own variant, so a session's world is
/// always the one its variant describes; the suite resets it before every suite.
public static func use(_ variant: AppVariant) {
current = variant
LeakCatalogue.format = variant.coachFormat
}
/// The same distinction as the coach draws it.
public var coachFormat: CoachFormat { format == .cash ? .cash : .tournament }
/// A settings key under this product's prefix.
public func key(_ suffix: String) -> String { defaultsPrefix + suffix }
public var isCash: Bool { format == .cash }
}