User: Ok, so this is what we got to.
//
// ContentView.swift
// BlackJackPractice
//
// Created by Victor Quach on 3/22/23.
//
import SwiftUI
struct SplitsView: View {
@State private var playerCard1: Int = 11
@State private var playerCard2: Int = 11
@State private var dealerCard: Int = 7
@State private var feedbackMessage: String = ""
func cardName(_ card: Int) -> String {
switch card {
case 1:
return "A"
case 10:
return "T"
case 11:
return "J"
case 12:
return "Q"
case 13:
return "K"
default:
return String(card)
}
}
func randomCard() -> Int {
let cardValues = [1,2,3,4,5,6,7,8,9,10,11,12,13]
return cardValues.randomElement()!
}
func shouldSplitCards(_ playerCard1: Int, _ playerCard2: Int, _ dealerCard: Int) -> (Bool, String) {
assert(playerCard1 == playerCard2, "The player cards should be the same");
// Always split aces and 8s
if playerCard1 == 1 || playerCard1 == 8 {
return (true, "Always split aces and 8s.")
}
// Never split 10s and 5s
if playerCard1 == 5 || playerCard2 >= 10 {
return (false, "Never split 10s and 5s.")
}
// Split 2s, 3s, 7s against 2-7
if playerCard1 == 2 || playerCard1 == 3 || playerCard1 == 7 {
if dealerCard >= 2 && dealerCard <= 7 {
return (true, "Split 2s, 3s, 7s against 2-7.")
} else {
return (false, "Split 2s, 3s, 7s against 2-7.")
}
}
// Split 4s against 5 or 6
if playerCard1 == 4 {
if dealerCard == 5 || dealerCard == 6 {
return (true, "Split 4s against 5 or 6.")
} else {
return (false, "Split 4s against 5 or 6.")
}
}
// Split 6s against 2-6
if playerCard1 == 6 {
if dealerCard >= 2 && dealerCard <= 6 {
return (true, "Split 6s against 2-6.")
} else {
return (false, "Split 6s against 2-6.")
}
}
// Split 9s against 2-6 and 8-9
if playerCard1 == 9 {
if dealerCard >= 2 && dealerCard <= 6 || dealerCard >= 8 && dealerCard <= 9 {
return (true, "Split 9s against 2-6 and 8-9.")
} else {
return (false, "Split 9s against 2-6 and 8-9.")
}
}
return (false, "No specific rule for this situation.")
}
var body: some View {
VStack(spacing: 16) {
Text(feedbackMessage)
Text("Dealer: \(cardName(dealerCard))")
Text("You: \(cardName(playerCard1))\(cardName(playerCard2))")
HStack {
Button(action: {
let (shouldSplit, reason) = shouldSplitCards(playerCard1, playerCard2, dealerCard)
feedbackMessage = shouldSplit ? "Correct! \(reason)" : "Incorrect. \(reason)"
}) {
Text("Split")
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(8)
}
Button(action: {
let (shouldSplit, reason) = shouldSplitCards(playerCard1, playerCard2, dealerCard)
feedbackMessage = !shouldSplit ? "Correct! \(reason)" : "Incorrect. \(reason)"
}) {
Text("Don't split")
.padding()
.background(Color.red)
.foregroundColor(.white)
.cornerRadius(8)
}
}
Button(action: {
// Generate new random cards for the player and dealer
playerCard1 = randomCard()
playerCard2 = playerCard1
dealerCard = randomCard()
// Clear the feedback message
feedbackMessage = ""
}) {
Text("New Cards")
.padding()
.background(Color.green)
.foregroundColor(.white)
.cornerRadius(8)
}
}
.padding()
}
}
struct SoftCountsView: View {
@State private var playerCard1: Int = 1
@State private var playerCard2: Int = 5
@State private var dealerCard: Int = 7
@State private var feedbackMessage: String = ""
enum Action: String {
case stand = "STAND"
case hit = "HIT"
case double = "DOUBLE"
case split = "SPLIT"
}
func cardName(_ card: Int) -> String {
switch card {
case 1:
return "A"
case 10:
return "T"
case 11:
return "J"
case 12:
return "Q"
case 13:
return "K"
default:
return String(card)
}
}
func randomCard() -> Int {
let cardValues = [1,2,3,4,5,6,7,8,9,10,11,12,13]
return cardValues.randomElement()!
}
func getActionOnSoftCount(playerCard1: Int, playerCard2: Int, dealerCard: Int) -> (action: Action, message: String) {
assert(playerCard1 == 1 || playerCard2 == 1)
if playerCard1 != 1 {
return getActionOnSoftCount(playerCard1: playerCard2, playerCard2: playerCard1, dealerCard: dealerCard)
}
if playerCard2 == 1 {
return (action: .split, message: "Always split aces")
}
if playerCard2 == 2 || playerCard2 == 3 {
if dealerCard >= 5 && dealerCard <= 6 {
return (action: .double, message: "Double soft 13-14 against 5-6, otw hit")
} else {
return (action: .hit, message: "Double soft 13-14 against 5-6, otw hit")
}
}
if playerCard2 == 4 || playerCard2 == 5 {
if dealerCard >= 4 && dealerCard <= 6 {
return (action: .double, message: "Double soft 15-16 against 4-6, otw hit")
} else {
return (action: .hit, message: "Double soft 15-16 against 4-6, otw hit")
}
}
if playerCard2 == 6 {
if dealerCard >= 3 && dealerCard <= 6 {
return (action: .double, message: "Double soft 17 against 3-6, otw hit")
} else {
return (action: .hit, message: "Double soft 17 against 3-6, otw hit")
}
}
if playerCard2 == 7 {
if dealerCard >= 2 && dealerCard <= 6 {
return (action: .double, message: "Soft 18: double against 2-6, stands against 7-8, hits against 9, 10, Ace. If it can't double against 2-6, it stands")
} else if dealerCard == 7 || dealerCard == 8 {
return (action: .stand, message: "Soft 18: double against 2-6, stands against 7-8, hits against 9, 10, Ace. If it can't double against 2-6, it stands")
} else {
return (action: .hit, message: "Soft 18: double against 2-6, stands against 7-8, hits against 9, 10, Ace. If it can't double against 2-6, it stands")
}
}
if playerCard2 == 8 {
if dealerCard == 6 {
return (action: .double, message: "Soft 19: double against 6, otw stand")
} else {
return (action: .stand, message: "Soft 19: double against 6, otw stand")
}
}
if playerCard2 >= 9 {
return (action: .stand, message: "Soft 20-21: stand")
}
return (action: .hit, message: "Unknown")
}
var body: some View {
VStack(spacing: 16) {
Text(feedbackMessage)
Text("Dealer: \(cardName(dealerCard))")
Text("You: \(cardName(playerCard1))\(cardName(playerCard2))")
VStack(spacing: 16) {
HStack(spacing: 16) {
Button(action: { // Stand
let (action, reason) = getActionOnSoftCount(playerCard1: playerCard1, playerCard2: playerCard2, dealerCard: dealerCard);
feedbackMessage = action == Action.stand ? "Correct! \(reason)" : "Incorrect. \(reason)"
}) {
Text("Stand")
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(8)
}
Button(action: { // Hit
let (action, reason) = getActionOnSoftCount(playerCard1: playerCard1, playerCard2: playerCard2, dealerCard: dealerCard);
feedbackMessage = action == Action.hit ? "Correct! \(reason)" : "Incorrect. \(reason)"
}) {
Text("Hit")
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(8)
}
}
HStack(spacing: 16) {
Button(action: { // Split
let (action, reason) = getActionOnSoftCount(playerCard1: playerCard1, playerCard2: playerCard2, dealerCard: dealerCard);
feedbackMessage = action == Action.split ? "Correct! \(reason)" : "Incorrect. \(reason)"
}) {
Text("Split")
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(8)
}
Button(action: { // Double
let (action, reason) = getActionOnSoftCount(playerCard1: playerCard1, playerCard2: playerCard2, dealerCard: dealerCard);
feedbackMessage = action == Action.double ? "Correct! \(reason)" : "Incorrect. \(reason)"
}) {
Text("Double")
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(8)
}
}
}
Button(action: {
// Generate new random cards for the player and dealer
playerCard1 = 1
playerCard2 = randomCard()
dealerCard = randomCard()
// Clear the feedback message
feedbackMessage = ""
}) {
Text("New Cards")
.padding()
.background(Color.green)
.foregroundColor(.white)
.cornerRadius(8)
}
}
.padding()
}
}
struct HardCountsView: View {
var body: some View {
Text("HardCounts")
}
}
struct ContentView: View {
var body: some View {
NavigationView {
MenuView()
}
}
}
struct MenuView: View {
let buttonWidth: CGFloat = 200
var body: some View {
VStack(spacing: 16) {
NavigationLink(destination: SplitsView()) {
Text("Splits")
.padding()
.frame(width: buttonWidth)
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(8)
}
NavigationLink(destination: SoftCountsView()) {
Text("Soft Counts")
.padding()
.frame(width: buttonWidth)
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(8)
}
NavigationLink(destination: HardCountsView()) {
Text("Hard Counts")
.padding()
.frame(width: buttonWidth)
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(8)
}
}
.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Seems like we should refactor this.
To refactor the code and make it more modular, you can create separate custom view components for the cards, buttons, and other elements. Here's a proposed continuation:
Create a CardView
component to display the cards:
struct CardView: View {
let card: Int
func cardName(_ card: Int) -> String {
switch card {
case 1:
return "A"
case 10:
return "T"
case 11:
return "J"
case 12:
return "Q"
case 13:
return "K"
default:
return String(card)
}
}
var body: some View {
Text(cardName(card))
}
}
Create a ActionButton
component for the action buttons:
struct ActionButton: View {
let title: String
let backgroundColor: Color
let action: () -> Void
var body: some View {
Button(action: action) {
Text(title)
.padding()
.background(backgroundColor)
.foregroundColor(.white)
.cornerRadius(8)
}
}
}
Replace the card display and buttons in SplitsView
and SoftCountsView
with the new components:
In SplitsView
:
Text("Dealer: ")
+ CardView(card: dealerCard)
Text("You: ")
+ CardView(card: playerCard1)
+ CardView(card: playerCard2)
// ...
ActionButton(title: "Split", backgroundColor: Color.blue, action: {
// ...
})
ActionButton(title: "Don't split", backgroundColor: Color.red, action: {
// ...
})
ActionButton(title: "New Cards", backgroundColor: Color.green, action: {
// ...
})
In SoftCountsView
:
Text("Dealer: ")
+ CardView(card: dealerCard)
Text("You: ")
+ CardView(card: playerCard1)
+ CardView(card: playerCard2)
// ...
ActionButton(title: "Stand", backgroundColor: Color.blue, action: {
// ...
})
ActionButton(title: "Hit", backgroundColor: Color.blue, action: {
// ...
})
ActionButton(title: "Split", backgroundColor: Color.blue, action: {
// ...
})
ActionButton(title: "Double", backgroundColor: Color.blue, action: {
// ...
})
ActionButton(title: "New Cards", backgroundColor: Color.green, action: {
// ...
})
This will make the code more readable and maintainable by separating the components and making them reusable.
User: Maybe we can refactor the views as well. There are repeated functions like randomCard
To refactor the views and eliminate the repeated randomCard
function, you can create a separate utility file to store common functions.
Create a new Swift file named Utility.swift
:
import Foundation
class Utility {
static func randomCard() -> Int {
let cardValues = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
return cardValues.randomElement()!
}
}
Now, you can replace the randomCard
function calls in both SplitsView
and SoftCountsView
with Utility.randomCard()
:
In SplitsView
:
Button(action: {
// Generate new random cards for the player and dealer
playerCard1 = Utility.randomCard()
playerCard2 = playerCard1
dealerCard = Utility.randomCard()
// Clear the feedback message
feedbackMessage = ""
}) {
Text("New Cards")
.padding()
.background(Color.green)
.foregroundColor(.white)
.cornerRadius(8)
}
In SoftCountsView
:
Button(action: {
// Generate new random cards for the player and dealer
playerCard1 = 1
playerCard2 = Utility.randomCard()
dealerCard = Utility.randomCard()
// Clear the feedback message
feedbackMessage = ""
}) {
Text("New Cards")
.padding()
.background(Color.green)
.foregroundColor(.white)
.cornerRadius(8)
}
This refactoring will make the code more organized and maintainable by centralizing the common functions in a utility class.
User: Wait a minute, I got the error: Cannot convert value of type 'CardView' to expected argument type 'Text'
Apologies for the confusion. To fix the error, you can use HStack
to properly display the CardView
components alongside the Text
components. Here's the updated code for SplitsView
and SoftCountsView
:
In SplitsView
:
HStack {
Text("Dealer: ")
CardView(card: dealerCard)
}
HStack {
Text("You: ")
CardView(card: playerCard1)
CardView(card: playerCard2)
}
// ...
ActionButton(title: "Split", backgroundColor: Color.blue, action: {
// ...
})
ActionButton(title: "Don't split", backgroundColor: Color.red, action: {
// ...
})
ActionButton(title: "New Cards", backgroundColor: Color.green, action: {
// ...
})
In SoftCountsView
:
HStack {
Text("Dealer: ")
CardView(card: dealerCard)
}
HStack {
Text("You: ")
CardView(card: playerCard1)
CardView(card: playerCard2)
}
// ...
ActionButton(title: "Stand", backgroundColor: Color.blue, action: {
// ...
})
ActionButton(title: "Hit", backgroundColor: Color.blue, action: {
// ...
})
ActionButton(title: "Split", backgroundColor: Color.blue, action: {
// ...
})
ActionButton(title: "Double", backgroundColor: Color.blue, action: {
// ...
})
ActionButton(title: "New Cards", backgroundColor: Color.green, action: {
// ...
})
By using HStack
, you can display the CardView
components horizontally alongside the Text
components without any errors.
User: How do I use CardView with constants? For instance CardView(card: 12)
does not work.
For example, the code
//
// CardView.swift
// BlackJackPractice
//
// Created by Victor Quach on 3/22/23.
//
import SwiftUI
struct CardView: View {
let card: Int
init(card: Int) {
self.card = card
}
func cardName(_ card: Int) -> String {
switch card {
case 1:
return "A"
case 10:
return "T"
case 11:
return "J"
case 12:
return "Q"
case 13:
return "K"
default:
return String(card)
}
}
var body: some View {
Text(cardName(card))
}
}
struct CardView_Previews: PreviewProvider {
static var previews: some View {
VStack {
CardView(card: 1)
CardView(card: 2)
CardView(card: 3)
CardView(card: 4)
CardView(card: 5)
CardView(card: 6)
CardView(card: 7)
CardView(card: 8)
CardView(card: 9)
CardView(card: 10)
CardView(card: 11)
CardView(card: 12)
CardView(card: 13)
}
}
}
fails with
extra arguments at positions #11, #12, #13 in call
----------------------------------------
CompileDylibError: Failed to build CardView.swift
Compiling failed: extra arguments at positions #11, #12, #13 in call
/Users/varal7/Projects/BlackJackPractice/BlackJackPractice/BlackJackPractice/CardView.swift:43:13: error: extra arguments at positions #11, #12, #13 in call
CardView(card: __designTimeInteger("#13530.[2].[0].property.[0].[0].arg[0].value.[0].arg[0].value", fallback: 1))
^
SwiftUI.ViewBuilder:3:24: note: 'buildBlock' declared here
public static func buildBlock<C0, C1, C2, C3, C4, C5, C6, C7, C8, C9>(_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3, _ c4: C4, _ c5: C5, _ c6: C6, _ c7: C7, _ c8: C8, _ c9: C9) -> TupleView<(C0, C1, C2, C3, C4, C5, C6, C7, C8, C9)> where C0 : View, C1 : View, C2 : View, C3 : View, C4 : View, C5 : View, C6 : View, C7 : View, C8 : View, C9 : View
^
What to do
The error occurs because the SwiftUI.ViewBuilder's buildBlock function supports up to 10 view elements. Since you have 13 CardView instances inside the VStack, it exceeds the limit.
To fix this issue, you can group CardView instances into multiple VStacks or HStacks and then combine them. Here's a modified version of the CardView_Previews struct:
struct CardView_Previews: PreviewProvider {
static var previews: some View {
VStack {
HStack {
CardView(card: 1)
CardView(card: 2)
CardView(card: 3)
CardView(card: 4)
CardView(card: 5)
}
HStack {
CardView(card: 6)
CardView(card: 7)
CardView(card: 8)
CardView(card: 9)
CardView(card: 10)
}
HStack {
CardView(card: 11)
CardView(card: 12)
CardView(card: 13)
}
}
}
}
This change will resolve the error and display all CardView instances as expected.