Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>

This commit is contained in:
Matt Bruce 2026-01-25 09:56:31 -06:00
parent 1e4e38b7ce
commit 77f5775cd2
11 changed files with 434 additions and 53 deletions

View File

@ -208,11 +208,12 @@ struct BaccaratEngine {
/// - Parameters:
/// - bet: The bet that was placed.
/// - result: The result of the round.
/// - variant: The baccarat variant being played (affects Banker payouts).
/// - Returns: The net winnings (positive), net loss (negative), or 0 for push.
func calculatePayout(bet: Bet, result: GameResult) -> Int {
func calculatePayout(bet: Bet, result: GameResult, variant: BaccaratVariant = .standard) -> Int {
switch bet.type {
case .player, .banker, .tie:
return calculateMainBetPayout(bet: bet, result: result)
return calculateMainBetPayout(bet: bet, result: result, variant: variant)
case .playerPair:
return playerHasPair ? Int(Double(bet.amount) * bet.type.payoutMultiplier) : -bet.amount
@ -229,15 +230,23 @@ struct BaccaratEngine {
}
/// Calculates payout for main bets (Player, Banker, Tie).
private func calculateMainBetPayout(bet: Bet, result: GameResult) -> Int {
private func calculateMainBetPayout(bet: Bet, result: GameResult, variant: BaccaratVariant) -> Int {
// Standard push logic (e.g., Tie on Player/Banker bets)
if result.isPush(for: bet.type) {
// Push - bet is returned
return 0
}
// Commission-Free: Banker wins with 6 is a push
if variant == .commissionFree
&& bet.type == .banker
&& result == .bankerWins
&& bankerHand.value == 6 {
return 0 // Push - bet returned
}
if result.isWinningBet(bet.type) {
// Win - return winnings based on payout multiplier
return Int(Double(bet.amount) * bet.type.payoutMultiplier)
// Win - return winnings based on variant-aware payout multiplier
return Int(Double(bet.amount) * bet.type.payoutMultiplier(for: variant))
} else {
// Loss - lose the bet amount
return -bet.amount

View File

@ -938,7 +938,7 @@ final class GameState: CasinoGameState {
var dragonBankerWon = false
for bet in currentBets {
let payout = engine.calculatePayout(bet: bet, result: result)
let payout = engine.calculatePayout(bet: bet, result: result, variant: settings.gameVariant)
totalWinnings += payout
// Track individual bet result

View File

@ -45,6 +45,19 @@ enum BetType: String, CaseIterable, Identifiable {
}
}
/// Returns the payout multiplier for this bet type based on the game variant.
/// - Parameter variant: The baccarat variant being played.
/// - Returns: The payout multiplier (e.g., 1.0 for 1:1, 0.95 for 5% commission).
func payoutMultiplier(for variant: BaccaratVariant) -> Double {
switch self {
case .banker:
// Commission-Free pays 1:1 (Banker 6 push handled separately in engine)
return variant == .commissionFree ? 1.0 : 0.95
default:
return payoutMultiplier
}
}
/// Display name with payout info.
var displayWithPayout: String {
switch self {
@ -82,6 +95,18 @@ enum BetType: String, CaseIterable, Identifiable {
}
}
/// Returns the payout description for this bet type based on the game variant.
/// - Parameter variant: The baccarat variant being played.
/// - Returns: The payout description string for display.
func payoutDescription(for variant: BaccaratVariant) -> String {
switch self {
case .banker:
return variant == .commissionFree ? "PAYS 1 TO 1" : "PAYS 0.95 TO 1"
default:
return payoutDescription
}
}
/// The color associated with this bet type.
var color: Color {
switch self {

View File

@ -67,6 +67,28 @@ enum RevealStyle: String, CaseIterable, Codable, Identifiable {
}
}
/// The baccarat game variant.
enum BaccaratVariant: String, CaseIterable, Identifiable, Codable {
case standard = "standard"
case commissionFree = "commissionFree"
var id: String { rawValue }
var displayName: String {
switch self {
case .standard: return String(localized: "Standard (5% Commission)")
case .commissionFree: return String(localized: "Commission Free")
}
}
var description: String {
switch self {
case .standard: return String(localized: "Banker pays 0.95:1")
case .commissionFree: return String(localized: "Banker pays 1:1, but Banker 6 pushes")
}
}
}
// TableLimits is now provided by CasinoKit
/// Observable settings class for Baccarat configuration.
@ -129,6 +151,11 @@ final class GameSettings: GameSettingsProtocol {
/// Whether to show streak alerts.
var showStreakAlerts: Bool = true
// MARK: - Game Variant Settings
/// The baccarat game variant (standard vs commission-free).
var gameVariant: BaccaratVariant = .standard
// MARK: - Sound Settings
/// Whether sound effects are enabled.
@ -157,6 +184,7 @@ final class GameSettings: GameSettingsProtocol {
static let revealStyle = "settings.revealStyle"
static let preferredRoadType = "settings.preferredRoadType"
static let showStreakAlerts = "settings.showStreakAlerts"
static let gameVariant = "settings.gameVariant"
}
// MARK: - iCloud
@ -286,6 +314,11 @@ final class GameSettings: GameSettingsProtocol {
if defaults.object(forKey: Keys.showStreakAlerts) != nil {
self.showStreakAlerts = defaults.bool(forKey: Keys.showStreakAlerts)
}
if let rawVariant = defaults.string(forKey: Keys.gameVariant),
let variant = BaccaratVariant(rawValue: rawVariant) {
self.gameVariant = variant
}
}
/// Loads settings from iCloud.
@ -351,6 +384,11 @@ final class GameSettings: GameSettingsProtocol {
if store.object(forKey: Keys.showStreakAlerts) != nil {
self.showStreakAlerts = store.bool(forKey: Keys.showStreakAlerts)
}
if let rawVariant = store.string(forKey: Keys.gameVariant),
let variant = BaccaratVariant(rawValue: rawVariant) {
self.gameVariant = variant
}
}
/// Saves settings to UserDefaults and iCloud.
@ -371,6 +409,7 @@ final class GameSettings: GameSettingsProtocol {
defaults.set(revealStyle.rawValue, forKey: Keys.revealStyle)
defaults.set(preferredRoadType.rawValue, forKey: Keys.preferredRoadType)
defaults.set(showStreakAlerts, forKey: Keys.showStreakAlerts)
defaults.set(gameVariant.rawValue, forKey: Keys.gameVariant)
// Also save to iCloud
if iCloudAvailable, let store = iCloudStore {
@ -388,6 +427,7 @@ final class GameSettings: GameSettingsProtocol {
store.set(revealStyle.rawValue, forKey: Keys.revealStyle)
store.set(preferredRoadType.rawValue, forKey: Keys.preferredRoadType)
store.set(showStreakAlerts, forKey: Keys.showStreakAlerts)
store.set(gameVariant.rawValue, forKey: Keys.gameVariant)
store.synchronize()
}
}
@ -426,6 +466,7 @@ final class GameSettings: GameSettingsProtocol {
revealStyle = .auto
preferredRoadType = .big
showStreakAlerts = true
gameVariant = .standard
save()
}
}

View File

@ -432,6 +432,10 @@
}
}
},
"6 PUSHES" : {
"comment" : "A note displayed for Banker 6 pushes in the baccarat betting table.",
"isCommentAutoGenerated" : true
},
"8 : 1" : {
"comment" : "The payout ratio for a tie bet.",
"localizations" : {
@ -671,6 +675,9 @@
}
}
}
},
"An alternative to standard Baccarat with simplified payouts." : {
},
"Analyzes patterns from the Big Road." : {
"comment" : "Tooltip text for the \"Big Eye Boy\" road type in the road map interface.",
@ -1094,6 +1101,9 @@
}
}
}
},
"Banker Bet: Pays 1:1 (no 5% commission)" : {
},
"Banker hand" : {
"comment" : "A label displayed above the banker's hand.",
@ -1141,6 +1151,14 @@
}
}
},
"Banker pays 0.95:1" : {
"comment" : "Description of the baccarat variant where the banker pays 0.95:1.",
"isCommentAutoGenerated" : true
},
"Banker pays 1:1, but Banker 6 pushes" : {
"comment" : "Description of the \"Commission Free\" baccarat variant.",
"isCommentAutoGenerated" : true
},
"Banker running hot (%lld%%)" : {
"comment" : "A hint to place a bet on the Banker based on the calculated house edge. The argument is the percentage of the house edge.",
"localizations" : {
@ -1257,6 +1275,10 @@
}
}
},
"Betting Hint" : {
"comment" : "An accessibility label for the combined trend and hint view.",
"isCommentAutoGenerated" : true
},
"Betting tips and trend analysis" : {
"comment" : "Description for hints feature.",
"localizations" : {
@ -1722,6 +1744,13 @@
"Color Meaning" : {
"comment" : "A heading displayed in the color legend of a derived road type popover.",
"isCommentAutoGenerated" : true
},
"Commission Free" : {
"comment" : "Description of a baccarat game variant where the banker always wins, regardless of the player's hand.",
"isCommentAutoGenerated" : true
},
"Commission-Free Mode" : {
},
"Compares current streak to 2 columns back." : {
"comment" : "Tooltip text for the \"Small Road\" road type in the Road Map selector.",
@ -2017,6 +2046,9 @@
}
}
}
},
"Enable in Settings → Game Variant." : {
},
"End Session" : {
"comment" : "The text for a button that ends the current game session.",
@ -2086,6 +2118,9 @@
}
}
}
},
"EXCEPT: If Banker wins with a total of 6, it's a push." : {
},
"Finest pattern detection. Looks 3 columns back." : {
"comment" : "Short description for a road type that compares the current streak to one from three columns ago.",
@ -2159,6 +2194,9 @@
}
}
}
},
"GAME VARIANT" : {
},
"Generate & Save Icons" : {
"comment" : "A button label that triggers the generation of app icons.",
@ -2419,6 +2457,9 @@
}
}
}
},
"House edge is slightly higher (1.46%) but payouts are easier." : {
},
"How to Export Icons" : {
"comment" : "A section header explaining how to export app icons.",
@ -3460,6 +3501,9 @@
}
}
}
},
"Player and Tie bets pay the same as standard Baccarat." : {
},
"Player Bet: Pays 1:1 (even money)" : {
"comment" : "Description of the payout for a Player Bet in the Rules Help view.",
@ -4447,6 +4491,10 @@
"comment" : "Name of the reveal style option that uses pressure-based card peeking.",
"isCommentAutoGenerated" : true
},
"Standard (5% Commission)" : {
"comment" : "Description of the \"Standard\" baccarat variant, including the commission rate.",
"isCommentAutoGenerated" : true
},
"STARTING BALANCE" : {
"comment" : "Section header for starting balance settings.",
"localizations" : {
@ -5804,6 +5852,9 @@
}
}
}
},
"Your Banker bet is returned — you don't win or lose." : {
}
},
"version" : "1.1"

View File

@ -324,14 +324,6 @@ struct GameTableView: View, SherpaDelegate {
// Betting table - completely hidden during dealing
if !isDealing {
// Trend badge when there's an active streak
TrendBadgeView(
streakType: state.currentStreakInfo.type,
streakCount: state.currentStreakInfo.count,
minimumStreak: 2
)
.padding(.bottom, Design.Spacing.xSmall)
BettingTableView(
gameState: state,
selectedChip: selectedChip
@ -341,17 +333,15 @@ struct GameTableView: View, SherpaDelegate {
.transition(.opacity)
.debugBorder(showDebugBorders, color: .blue, label: "BetTable")
// Betting hint (static, below table, above chips)
if let hintInfo = state.currentHintInfo {
BettingHintView(
hint: hintInfo.text,
secondaryInfo: hintInfo.secondaryText,
style: hintInfo.style
// Combined trend and hint view (static, below table, above chips)
CombinedTrendHintView(
streakType: state.currentStreakInfo.type,
streakCount: state.currentStreakInfo.count,
hintInfo: state.currentHintInfo
)
.transition(.opacity)
.padding(.vertical, Design.Spacing.small)
.debugBorder(showDebugBorders, color: .purple, label: "Hint")
}
.debugBorder(showDebugBorders, color: .purple, label: "TrendHint")
}
Spacer(minLength: Design.Spacing.xSmall)
@ -458,14 +448,6 @@ struct GameTableView: View, SherpaDelegate {
// Betting table - completely hidden during dealing
if !isDealing {
// Trend badge when there's an active streak
TrendBadgeView(
streakType: state.currentStreakInfo.type,
streakCount: state.currentStreakInfo.count,
minimumStreak: 2
)
.padding(.bottom, Design.Spacing.xSmall)
BettingTableView(
gameState: state,
selectedChip: selectedChip
@ -475,17 +457,15 @@ struct GameTableView: View, SherpaDelegate {
.transition(.opacity)
.debugBorder(showDebugBorders, color: .blue, label: "BetTable")
// Betting hint (static, below table, above chips)
if let hintInfo = state.currentHintInfo {
BettingHintView(
hint: hintInfo.text,
secondaryInfo: hintInfo.secondaryText,
style: hintInfo.style
// Combined trend and hint view (static, below table, above chips)
CombinedTrendHintView(
streakType: state.currentStreakInfo.type,
streakCount: state.currentStreakInfo.count,
hintInfo: state.currentHintInfo
)
.transition(.opacity)
.padding(.vertical, Design.Spacing.small)
.debugBorder(showDebugBorders, color: .purple, label: "Hint")
}
.debugBorder(showDebugBorders, color: .purple, label: "TrendHint")
}
// Chip selector - only shown during betting phase

View File

@ -65,6 +65,19 @@ struct RulesHelpView: View {
String(localized: "Banker bet has the lowest house edge (1.06%).")
]
),
RulePage(
title: String(localized: "Commission-Free Mode"),
icon: "sparkles",
content: [
String(localized: "An alternative to standard Baccarat with simplified payouts."),
String(localized: "Banker Bet: Pays 1:1 (no 5% commission)"),
String(localized: "EXCEPT: If Banker wins with a total of 6, it's a push."),
String(localized: "Your Banker bet is returned — you don't win or lose."),
String(localized: "Player and Tie bets pay the same as standard Baccarat."),
String(localized: "House edge is slightly higher (1.46%) but payouts are easier."),
String(localized: "Enable in Settings → Game Variant.")
]
),
RulePage(
title: String(localized: "Third Card - Player"),
icon: "hand.draw.fill",

View File

@ -41,7 +41,15 @@ struct SettingsView: View {
}
}
// 2. Deck Settings
// 2. Game Variant
SheetSection(title: String(localized: "GAME VARIANT"), icon: "sparkles") {
GameVariantPicker(selection: $settings.gameVariant)
.onChange(of: settings.gameVariant) { _, _ in
hasChanges = true
}
}
// 3. Deck Settings
SheetSection(title: String(localized: "DECK SETTINGS"), icon: "rectangle.portrait.on.rectangle.portrait") {
DeckCountPicker(selection: $settings.deckCount)
.onChange(of: settings.deckCount) { _, _ in
@ -476,6 +484,26 @@ struct RevealStylePicker: View {
}
}
// MARK: - Game Variant Picker (Baccarat-specific)
struct GameVariantPicker: View {
@Binding var selection: BaccaratVariant
var body: some View {
VStack(spacing: Design.Spacing.small) {
ForEach(BaccaratVariant.allCases) { variant in
SelectableRow(
title: variant.displayName,
subtitle: variant.description,
isSelected: selection == variant,
accentColor: Color.Sheet.accent,
action: { selection = variant }
)
}
}
}
}
#Preview {
SettingsView(settings: GameSettings(), gameState: GameState()) { }
}

View File

@ -68,6 +68,11 @@ struct BettingTableView: View {
)
}
/// Banker payout text depends on game variant
private var bankerPayoutText: String {
gameState.settings.gameVariant == .commissionFree ? "1 : 1" : "0.95 : 1"
}
// Use global debug flag from Design constants
private var showDebugBorders: Bool { Design.showDebugBorders }
@ -112,7 +117,7 @@ struct BettingTableView: View {
// Middle row: BANKER | BONUS
MainBetRow(
title: "BANKER",
payoutText: "0.95 : 1",
payoutText: bankerPayoutText,
mainBetAmount: betAmount(for: .banker),
bonusBetAmount: betAmount(for: .dragonBonusBanker),
isSelected: isBankerSelected,
@ -122,6 +127,7 @@ struct BettingTableView: View {
isBonusAtMax: isAtMax(for: .dragonBonusBanker),
mainColor: Color.BettingZone.bankerDark,
rowHeight: mainRowHeight,
showBanker6Note: gameState.settings.gameVariant == .commissionFree,
onMain: { gameState.placeBet(type: .banker, amount: selectedChip.rawValue) },
onBonus: { gameState.placeBet(type: .dragonBonusBanker, amount: selectedChip.rawValue) }
)
@ -145,6 +151,7 @@ struct BettingTableView: View {
isBonusAtMax: isAtMax(for: .dragonBonusPlayer),
mainColor: Color.BettingZone.playerDark,
rowHeight: mainRowHeight,
showBanker6Note: false,
onMain: { gameState.placeBet(type: .player, amount: selectedChip.rawValue) },
onBonus: { gameState.placeBet(type: .dragonBonusPlayer, amount: selectedChip.rawValue) }
)
@ -337,6 +344,7 @@ private struct MainBetRow: View {
let isBonusAtMax: Bool
let mainColor: Color
let rowHeight: CGFloat
let showBanker6Note: Bool
let onMain: () -> Void
let onBonus: () -> Void
@ -351,6 +359,7 @@ private struct MainBetRow: View {
isEnabled: canBetMain,
isAtMax: isMainAtMax,
color: mainColor,
showBanker6Note: showBanker6Note,
action: onMain
)
@ -382,8 +391,24 @@ private struct MainBetZone: View {
let isEnabled: Bool
let isAtMax: Bool
let color: Color
let showBanker6Note: Bool
let action: () -> Void
/// Accessibility label with Banker 6 note if applicable
private var accessibilityLabelText: String {
var label = "\(title) bet, pays \(payoutText)"
if showBanker6Note {
label += ", Banker 6 pushes"
}
if isSelected {
label += ", selected"
}
if betAmount > 0 {
label += ", current bet $\(betAmount)"
}
return label
}
var body: some View {
Button {
if isEnabled { action() }
@ -407,12 +432,27 @@ private struct MainBetZone: View {
Text(title)
.font(.system(size: Design.BaseFontSize.xLarge, weight: .black, design: .rounded))
.tracking(2)
.foregroundStyle(.white)
// Payout line - combined with 6 pushes note for Commission-Free
if showBanker6Note {
HStack(spacing: Design.Spacing.xSmall) {
Text(payoutText)
.foregroundStyle(.white.opacity(Design.Opacity.strong))
Text("")
.foregroundStyle(.white.opacity(Design.Opacity.medium))
Text("6 PUSHES")
.foregroundStyle(.yellow.opacity(Design.Opacity.heavy))
}
.font(.system(size: Design.BaseFontSize.callout - 2, weight: .semibold, design: .rounded))
} else {
Text(payoutText)
.font(.system(size: Design.BaseFontSize.callout - 2, weight: .semibold, design: .rounded))
.opacity(Design.Opacity.strong)
.foregroundStyle(.white.opacity(Design.Opacity.strong))
}
}
.foregroundStyle(.white)
// Chip indicator - overlaid on right, doesn't affect centering
if betAmount > 0 {
@ -426,7 +466,7 @@ private struct MainBetZone: View {
}
.buttonStyle(.plain)
.accessibilityElement(children: .ignore)
.accessibilityLabel("\(title) bet, pays \(payoutText)" + (isSelected ? ", selected" : "") + (betAmount > 0 ? ", current bet $\(betAmount)" : ""))
.accessibilityLabel(accessibilityLabelText)
.accessibilityAddTraits(.isButton)
}
}

View File

@ -167,8 +167,202 @@ struct StreakBadgeView: View {
}
}
// MARK: - Combined Trend and Hint View
/// A combined view showing streak information and betting hints side-by-side.
/// Displays streak badge on the left (when present) and hint text on the right.
struct CombinedTrendHintView: View {
/// The current streak type (nil if no streak).
let streakType: GameResult?
/// The streak count.
let streakCount: Int
/// Minimum streak to display.
var minimumStreak: Int = 2
/// The hint information (nil if no hint to show).
let hintInfo: GameState.HintInfo?
// MARK: - Scaled Metrics
@ScaledMetric(relativeTo: .body) private var iconSize: CGFloat = CasinoDesign.HintSize.iconSize
@ScaledMetric(relativeTo: .body) private var fontSize: CGFloat = CasinoDesign.HintSize.fontSize
@ScaledMetric(relativeTo: .body) private var paddingH: CGFloat = CasinoDesign.HintSize.paddingH
@ScaledMetric(relativeTo: .body) private var paddingV: CGFloat = CasinoDesign.HintSize.paddingV
@ScaledMetric(relativeTo: .caption) private var streakFontSize: CGFloat = Design.BaseFontSize.small
// MARK: - Computed Properties
private var shouldShowStreak: Bool {
streakType != nil && streakType != .tie && streakCount >= minimumStreak
}
private var hasContent: Bool {
shouldShowStreak || hintInfo != nil
}
private var streakColor: Color {
guard let type = streakType else { return .clear }
return type == .bankerWins ? .red : .blue
}
private var streakText: String {
guard let type = streakType else { return "" }
let letter = type == .bankerWins ? "B" : "P"
return "\(letter)×\(streakCount)"
}
private var isHotStreak: Bool {
streakCount >= 4
}
// MARK: - Body
var body: some View {
if hasContent {
HStack(spacing: CasinoDesign.Spacing.small) {
// Streak badge (left side)
if shouldShowStreak {
streakBadge
// Separator when both are shown
if hintInfo != nil {
Text("")
.font(.system(size: fontSize, weight: .bold))
.foregroundStyle(.white.opacity(Design.Opacity.medium))
}
}
// Hint content (right side)
if let hint = hintInfo {
hintContent(hint: hint)
}
}
.padding(.horizontal, paddingH)
.padding(.vertical, paddingV)
.frame(minWidth: CasinoDesign.HintSize.minWidth, alignment: .center)
.background(
Capsule()
.fill(Color.black.opacity(CasinoDesign.Opacity.heavy))
.overlay(
Capsule()
.strokeBorder(borderColor.opacity(CasinoDesign.Opacity.medium), lineWidth: CasinoDesign.LineWidth.thin)
)
)
.shadow(color: .black.opacity(CasinoDesign.Opacity.medium), radius: CasinoDesign.Shadow.radiusMedium)
.accessibilityElement(children: .combine)
}
}
// MARK: - Subviews
private var streakBadge: some View {
HStack(spacing: Design.Spacing.xxSmall) {
Image(systemName: "flame.fill")
.font(.system(size: streakFontSize))
.foregroundStyle(isHotStreak ? .yellow : .orange)
Text(streakText)
.font(.system(size: streakFontSize, weight: .bold, design: .rounded))
.foregroundStyle(streakColor)
}
.accessibilityLabel(streakAccessibilityLabel)
}
private func hintContent(hint: GameState.HintInfo) -> some View {
HStack(spacing: CasinoDesign.Spacing.small) {
Image(systemName: hint.style.icon)
.font(.system(size: iconSize))
.foregroundStyle(hint.style.color)
VStack(alignment: .leading, spacing: CasinoDesign.Spacing.xxSmall) {
Text(hint.text)
.font(.system(size: fontSize, weight: .medium))
.foregroundStyle(.white)
.lineLimit(1)
.minimumScaleFactor(CasinoDesign.MinScaleFactor.comfortable)
if let secondary = hint.secondaryText {
Text(secondary)
.font(.system(size: fontSize - 2, weight: .regular))
.foregroundStyle(.white.opacity(CasinoDesign.Opacity.medium))
.lineLimit(1)
}
}
}
.accessibilityLabel(String(localized: "Betting Hint"))
.accessibilityValue(hint.text)
}
// MARK: - Helpers
private var borderColor: Color {
if shouldShowStreak {
return streakColor
} else if let hint = hintInfo {
return hint.style.color
}
return .white
}
private var streakAccessibilityLabel: String {
guard let type = streakType else { return "" }
let sideName = type == .bankerWins ?
String(localized: "Banker") : String(localized: "Player")
return String(localized: "\(sideName) streak of \(streakCount)")
}
}
// MARK: - Previews
#Preview("Combined - Both") {
ZStack {
Color.Table.felt.ignoresSafeArea()
CombinedTrendHintView(
streakType: .bankerWins,
streakCount: 5,
hintInfo: GameState.HintInfo(
text: "Banker running hot (65%)",
secondaryText: nil,
isStreak: false,
isChoppy: false,
isBankerHot: true,
isPlayerHot: false
)
)
}
}
#Preview("Combined - Streak Only") {
ZStack {
Color.Table.felt.ignoresSafeArea()
CombinedTrendHintView(
streakType: .playerWins,
streakCount: 4,
hintInfo: nil
)
}
}
#Preview("Combined - Hint Only") {
ZStack {
Color.Table.felt.ignoresSafeArea()
CombinedTrendHintView(
streakType: nil,
streakCount: 0,
hintInfo: GameState.HintInfo(
text: "Banker has the lowest house edge",
secondaryText: nil,
isStreak: false,
isChoppy: false,
isBankerHot: false,
isPlayerHot: false
)
)
}
}
#Preview("Distribution Bar") {
ZStack {
Color.Table.felt.ignoresSafeArea()

View File

@ -22,7 +22,7 @@ public enum BettingHintStyle {
/// Custom color
case custom(Color, String)
var color: Color {
public var color: Color {
switch self {
case .positive: return .green
case .negative: return .red
@ -33,7 +33,7 @@ public enum BettingHintStyle {
}
}
var icon: String {
public var icon: String {
switch self {
case .positive: return "arrow.up.circle.fill"
case .negative: return "arrow.down.circle.fill"