Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
c9b2a9e692
commit
ed1dabfe18
@ -591,6 +591,22 @@ final class GameState: CasinoGameState {
|
|||||||
return currentAmount + amount <= maxBet
|
return currentAmount + amount <= maxBet
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The minimum bet level across all bet types that can accept more chips.
|
||||||
|
/// Used by chip selector to determine if chips should be enabled.
|
||||||
|
/// Returns the smallest bet so chips stay enabled if ANY bet type can accept more.
|
||||||
|
var minBetForChipSelector: Int {
|
||||||
|
// All bet types are always available in Baccarat
|
||||||
|
let allBetTypes: [BetType] = [
|
||||||
|
.player, .banker, .tie,
|
||||||
|
.playerPair, .bankerPair,
|
||||||
|
.dragonBonusPlayer, .dragonBonusBanker
|
||||||
|
]
|
||||||
|
|
||||||
|
// Return the minimum bet amount across all bet types
|
||||||
|
// so chips stay enabled if any bet type can accept more
|
||||||
|
return allBetTypes.map { betAmount(for: $0) }.min() ?? 0
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Betting Actions
|
// MARK: - Betting Actions
|
||||||
|
|
||||||
/// Places a bet of the specified amount on the given bet type.
|
/// Places a bet of the specified amount on the given bet type.
|
||||||
|
|||||||
@ -353,7 +353,7 @@ struct GameTableView: View {
|
|||||||
ChipSelectorView(
|
ChipSelectorView(
|
||||||
selectedChip: $selectedChip,
|
selectedChip: $selectedChip,
|
||||||
balance: state.balance,
|
balance: state.balance,
|
||||||
currentBet: state.totalBetAmount,
|
currentBet: state.minBetForChipSelector,
|
||||||
maxBet: state.maxBet
|
maxBet: state.maxBet
|
||||||
)
|
)
|
||||||
.transition(.opacity.combined(with: .move(edge: .bottom)))
|
.transition(.opacity.combined(with: .move(edge: .bottom)))
|
||||||
@ -464,7 +464,7 @@ struct GameTableView: View {
|
|||||||
ChipSelectorView(
|
ChipSelectorView(
|
||||||
selectedChip: $selectedChip,
|
selectedChip: $selectedChip,
|
||||||
balance: state.balance,
|
balance: state.balance,
|
||||||
currentBet: state.totalBetAmount,
|
currentBet: state.minBetForChipSelector,
|
||||||
maxBet: state.maxBet
|
maxBet: state.maxBet
|
||||||
)
|
)
|
||||||
.transition(.opacity.combined(with: .move(edge: .bottom)))
|
.transition(.opacity.combined(with: .move(edge: .bottom)))
|
||||||
|
|||||||
@ -177,7 +177,6 @@ final class GameState: CasinoGameState {
|
|||||||
/// Whether a specific side bet can accept more chips of the given amount.
|
/// Whether a specific side bet can accept more chips of the given amount.
|
||||||
/// Matches Baccarat's canAddToBet pattern.
|
/// Matches Baccarat's canAddToBet pattern.
|
||||||
func canAddToSideBet(type: SideBetType, amount: Int) -> Bool {
|
func canAddToSideBet(type: SideBetType, amount: Int) -> Bool {
|
||||||
guard settings.sideBetsEnabled else { return false }
|
|
||||||
let currentAmount: Int
|
let currentAmount: Int
|
||||||
switch type {
|
switch type {
|
||||||
case .perfectPairs:
|
case .perfectPairs:
|
||||||
@ -204,16 +203,12 @@ final class GameState: CasinoGameState {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The minimum bet level across all active bet types.
|
/// The minimum bet level across all bet types.
|
||||||
/// Used by chip selector to determine if chips should be enabled.
|
/// Used by chip selector to determine if chips should be enabled.
|
||||||
/// Returns the smallest bet so chips stay enabled if ANY bet type can accept more.
|
/// Returns the smallest bet so chips stay enabled if ANY bet type can accept more.
|
||||||
var minBetForChipSelector: Int {
|
var minBetForChipSelector: Int {
|
||||||
if settings.sideBetsEnabled {
|
// Return the minimum of all bet types so chips stay enabled if any can be increased
|
||||||
// Return the minimum of all bet types so chips stay enabled if any can be increased
|
min(currentBet, perfectPairsBet, twentyOnePlusThreeBet)
|
||||||
return min(currentBet, perfectPairsBet, twentyOnePlusThreeBet)
|
|
||||||
} else {
|
|
||||||
return currentBet
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether the current hand can hit.
|
/// Whether the current hand can hit.
|
||||||
@ -983,8 +978,6 @@ final class GameState: CasinoGameState {
|
|||||||
|
|
||||||
/// Evaluates side bets based on the initial deal.
|
/// Evaluates side bets based on the initial deal.
|
||||||
private func evaluateSideBets() {
|
private func evaluateSideBets() {
|
||||||
guard settings.sideBetsEnabled else { return }
|
|
||||||
|
|
||||||
let playerCards = playerHands[0].cards
|
let playerCards = playerHands[0].cards
|
||||||
guard playerCards.count >= 2 else { return }
|
guard playerCards.count >= 2 else { return }
|
||||||
|
|
||||||
|
|||||||
@ -136,11 +136,6 @@ final class GameSettings: GameSettingsProtocol {
|
|||||||
/// Speed of card dealing (uses CasinoDesign.DealingSpeed constants)
|
/// Speed of card dealing (uses CasinoDesign.DealingSpeed constants)
|
||||||
var dealingSpeed: Double = CasinoDesign.DealingSpeed.normal { didSet { save() } }
|
var dealingSpeed: Double = CasinoDesign.DealingSpeed.normal { didSet { save() } }
|
||||||
|
|
||||||
// MARK: - Side Bets
|
|
||||||
|
|
||||||
/// Whether side bets (Perfect Pairs, 21+3) are enabled.
|
|
||||||
var sideBetsEnabled: Bool = false { didSet { save() } }
|
|
||||||
|
|
||||||
// MARK: - Display Settings
|
// MARK: - Display Settings
|
||||||
|
|
||||||
/// Whether to show the cards remaining indicator.
|
/// Whether to show the cards remaining indicator.
|
||||||
@ -242,7 +237,6 @@ final class GameSettings: GameSettingsProtocol {
|
|||||||
self.blackjackPayout = data.blackjackPayout
|
self.blackjackPayout = data.blackjackPayout
|
||||||
self.insuranceAllowed = data.insuranceAllowed
|
self.insuranceAllowed = data.insuranceAllowed
|
||||||
self.neverAskInsurance = data.neverAskInsurance
|
self.neverAskInsurance = data.neverAskInsurance
|
||||||
self.sideBetsEnabled = data.sideBetsEnabled
|
|
||||||
self.showAnimations = data.showAnimations
|
self.showAnimations = data.showAnimations
|
||||||
self.dealingSpeed = data.dealingSpeed
|
self.dealingSpeed = data.dealingSpeed
|
||||||
self.showCardsRemaining = data.showCardsRemaining
|
self.showCardsRemaining = data.showCardsRemaining
|
||||||
@ -269,7 +263,6 @@ final class GameSettings: GameSettingsProtocol {
|
|||||||
blackjackPayout: blackjackPayout,
|
blackjackPayout: blackjackPayout,
|
||||||
insuranceAllowed: insuranceAllowed,
|
insuranceAllowed: insuranceAllowed,
|
||||||
neverAskInsurance: neverAskInsurance,
|
neverAskInsurance: neverAskInsurance,
|
||||||
sideBetsEnabled: sideBetsEnabled,
|
|
||||||
showAnimations: showAnimations,
|
showAnimations: showAnimations,
|
||||||
dealingSpeed: dealingSpeed,
|
dealingSpeed: dealingSpeed,
|
||||||
showCardsRemaining: showCardsRemaining,
|
showCardsRemaining: showCardsRemaining,
|
||||||
@ -297,7 +290,6 @@ final class GameSettings: GameSettingsProtocol {
|
|||||||
blackjackPayout = 1.5
|
blackjackPayout = 1.5
|
||||||
insuranceAllowed = true
|
insuranceAllowed = true
|
||||||
neverAskInsurance = false
|
neverAskInsurance = false
|
||||||
sideBetsEnabled = false
|
|
||||||
showAnimations = true
|
showAnimations = true
|
||||||
dealingSpeed = CasinoDesign.DealingSpeed.normal
|
dealingSpeed = CasinoDesign.DealingSpeed.normal
|
||||||
showCardsRemaining = true
|
showCardsRemaining = true
|
||||||
|
|||||||
@ -3250,6 +3250,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Enable Side Bets" : {
|
"Enable Side Bets" : {
|
||||||
|
"extractionState" : "stale",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"en" : {
|
"en" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
@ -5348,6 +5349,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Perfect Pairs (25:1) and 21+3 (100:1)" : {
|
"Perfect Pairs (25:1) and 21+3 (100:1)" : {
|
||||||
|
"extractionState" : "stale",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"en" : {
|
"en" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
@ -6329,6 +6331,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"SIDE BETS" : {
|
"SIDE BETS" : {
|
||||||
|
"extractionState" : "stale",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"en" : {
|
"en" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
|
|||||||
@ -62,7 +62,6 @@ struct BlackjackSettingsData: PersistableGameData {
|
|||||||
blackjackPayout: 1.5,
|
blackjackPayout: 1.5,
|
||||||
insuranceAllowed: true,
|
insuranceAllowed: true,
|
||||||
neverAskInsurance: false,
|
neverAskInsurance: false,
|
||||||
sideBetsEnabled: false,
|
|
||||||
showAnimations: true,
|
showAnimations: true,
|
||||||
dealingSpeed: 1.0,
|
dealingSpeed: 1.0,
|
||||||
showCardsRemaining: true,
|
showCardsRemaining: true,
|
||||||
@ -87,7 +86,6 @@ struct BlackjackSettingsData: PersistableGameData {
|
|||||||
var blackjackPayout: Double
|
var blackjackPayout: Double
|
||||||
var insuranceAllowed: Bool
|
var insuranceAllowed: Bool
|
||||||
var neverAskInsurance: Bool
|
var neverAskInsurance: Bool
|
||||||
var sideBetsEnabled: Bool
|
|
||||||
var showAnimations: Bool
|
var showAnimations: Bool
|
||||||
var dealingSpeed: Double
|
var dealingSpeed: Double
|
||||||
var showCardsRemaining: Bool
|
var showCardsRemaining: Bool
|
||||||
|
|||||||
@ -111,16 +111,6 @@ struct SettingsView: View {
|
|||||||
TableLimitsPicker(selection: $settings.tableLimits)
|
TableLimitsPicker(selection: $settings.tableLimits)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3.5. Side Bets
|
|
||||||
SheetSection(title: String(localized: "SIDE BETS"), icon: "dollarsign.arrow.trianglehead.counterclockwise.rotate.90") {
|
|
||||||
SettingsToggle(
|
|
||||||
title: String(localized: "Enable Side Bets"),
|
|
||||||
subtitle: String(localized: "Perfect Pairs (25:1) and 21+3 (100:1)"),
|
|
||||||
isOn: $settings.sideBetsEnabled,
|
|
||||||
accentColor: accent
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. Deck Settings
|
// 4. Deck Settings
|
||||||
SheetSection(title: String(localized: "DECK SETTINGS"), icon: "rectangle.portrait.on.rectangle.portrait") {
|
SheetSection(title: String(localized: "DECK SETTINGS"), icon: "rectangle.portrait.on.rectangle.portrait") {
|
||||||
DeckCountPicker(selection: $settings.deckCount)
|
DeckCountPicker(selection: $settings.deckCount)
|
||||||
|
|||||||
@ -55,38 +55,32 @@ struct BettingZoneView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
if state.settings.sideBetsEnabled {
|
// Horizontal layout: PP | Main Bet | 21+3
|
||||||
// Horizontal layout: PP | Main Bet | 21+3
|
HStack(spacing: Design.Spacing.small) {
|
||||||
HStack(spacing: Design.Spacing.small) {
|
// Perfect Pairs
|
||||||
// Perfect Pairs
|
SideBetZoneView(
|
||||||
SideBetZoneView(
|
betType: .perfectPairs,
|
||||||
betType: .perfectPairs,
|
betAmount: state.perfectPairsBet,
|
||||||
betAmount: state.perfectPairsBet,
|
isEnabled: canAddPerfectPairs,
|
||||||
isEnabled: canAddPerfectPairs,
|
isAtMax: isPPAtMax,
|
||||||
isAtMax: isPPAtMax,
|
onTap: { state.placeSideBet(type: .perfectPairs, amount: selectedChip.rawValue) }
|
||||||
onTap: { state.placeSideBet(type: .perfectPairs, amount: selectedChip.rawValue) }
|
)
|
||||||
)
|
.frame(width: Design.Size.sideBetZoneWidth)
|
||||||
.frame(width: Design.Size.sideBetZoneWidth)
|
|
||||||
|
|
||||||
// Main bet (center, takes remaining space)
|
// Main bet (center, takes remaining space)
|
||||||
mainBetZone
|
|
||||||
|
|
||||||
// 21+3
|
|
||||||
SideBetZoneView(
|
|
||||||
betType: .twentyOnePlusThree,
|
|
||||||
betAmount: state.twentyOnePlusThreeBet,
|
|
||||||
isEnabled: canAddTwentyOnePlusThree,
|
|
||||||
isAtMax: is21PlusThreeAtMax,
|
|
||||||
onTap: { state.placeSideBet(type: .twentyOnePlusThree, amount: selectedChip.rawValue) }
|
|
||||||
)
|
|
||||||
.frame(width: Design.Size.sideBetZoneWidth)
|
|
||||||
}
|
|
||||||
.frame(height: zoneHeight)
|
|
||||||
} else {
|
|
||||||
// Simple layout: just main bet
|
|
||||||
mainBetZone
|
mainBetZone
|
||||||
.frame(height: zoneHeight)
|
|
||||||
|
// 21+3
|
||||||
|
SideBetZoneView(
|
||||||
|
betType: .twentyOnePlusThree,
|
||||||
|
betAmount: state.twentyOnePlusThreeBet,
|
||||||
|
isEnabled: canAddTwentyOnePlusThree,
|
||||||
|
isAtMax: is21PlusThreeAtMax,
|
||||||
|
onTap: { state.placeSideBet(type: .twentyOnePlusThree, amount: selectedChip.rawValue) }
|
||||||
|
)
|
||||||
|
.frame(width: Design.Size.sideBetZoneWidth)
|
||||||
}
|
}
|
||||||
|
.frame(height: zoneHeight)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var mainBetZone: some View {
|
private var mainBetZone: some View {
|
||||||
@ -155,14 +149,12 @@ extension Design.Size {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#Preview("With Side Bets") {
|
#Preview("Main Bet Only") {
|
||||||
ZStack {
|
ZStack {
|
||||||
Color.Table.felt.ignoresSafeArea()
|
Color.Table.felt.ignoresSafeArea()
|
||||||
BettingZoneView(
|
BettingZoneView(
|
||||||
state: {
|
state: {
|
||||||
let settings = GameSettings()
|
let state = GameState(settings: GameSettings())
|
||||||
settings.sideBetsEnabled = true
|
|
||||||
let state = GameState(settings: settings)
|
|
||||||
state.placeBet(amount: 100)
|
state.placeBet(amount: 100)
|
||||||
return state
|
return state
|
||||||
}(),
|
}(),
|
||||||
@ -177,9 +169,7 @@ extension Design.Size {
|
|||||||
Color.Table.felt.ignoresSafeArea()
|
Color.Table.felt.ignoresSafeArea()
|
||||||
BettingZoneView(
|
BettingZoneView(
|
||||||
state: {
|
state: {
|
||||||
let settings = GameSettings()
|
let state = GameState(settings: GameSettings())
|
||||||
settings.sideBetsEnabled = true
|
|
||||||
let state = GameState(settings: settings)
|
|
||||||
state.placeBet(amount: 250)
|
state.placeBet(amount: 250)
|
||||||
state.placeSideBet(type: .perfectPairs, amount: 25)
|
state.placeSideBet(type: .perfectPairs, amount: 25)
|
||||||
state.placeSideBet(type: .twentyOnePlusThree, amount: 50)
|
state.placeSideBet(type: .twentyOnePlusThree, amount: 50)
|
||||||
|
|||||||
@ -135,7 +135,7 @@ struct BlackjackTableView: View {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Side bet toasts (positioned on left/right sides to not cover cards)
|
// Side bet toasts (positioned on left/right sides to not cover cards)
|
||||||
if state.settings.sideBetsEnabled && state.showSideBetToasts {
|
if state.showSideBetToasts {
|
||||||
HStack {
|
HStack {
|
||||||
// PP on left
|
// PP on left
|
||||||
if state.perfectPairsBet > 0, let ppResult = state.perfectPairsResult {
|
if state.perfectPairsBet > 0, let ppResult = state.perfectPairsResult {
|
||||||
|
|||||||
@ -36,6 +36,17 @@ public protocol CasinoGameState: SessionManagedGame {
|
|||||||
|
|
||||||
/// Aggregated statistics from all sessions.
|
/// Aggregated statistics from all sessions.
|
||||||
var aggregatedStats: AggregatedSessionStats { get }
|
var aggregatedStats: AggregatedSessionStats { get }
|
||||||
|
|
||||||
|
// MARK: - Chip Selection
|
||||||
|
|
||||||
|
/// The minimum bet level across all bet types.
|
||||||
|
/// Used by ChipSelectorView to determine if chips should be enabled.
|
||||||
|
/// Returns the smallest bet so chips stay enabled if ANY bet type can accept more.
|
||||||
|
///
|
||||||
|
/// Games with multiple bet types (main bet, side bets) should return the minimum
|
||||||
|
/// bet amount across all placeable bet types, ensuring the chip selector remains
|
||||||
|
/// responsive as long as at least one bet type has room for more chips.
|
||||||
|
var minBetForChipSelector: Int { get }
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Default Implementations
|
// MARK: - Default Implementations
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user