logo
Updated

iOS: Refactor (part 2)

Ok, so we made this View. Let's refactor it.


//
//  SoftCountsView.swift
//  BlackJackPractice
//
//  Created by Victor Quach on 3/22/23.
//

import SwiftUI


struct SoftCountsView: View {
    @State private var playerCard1: Int
    @State private var playerCard2: Int
    @State private var dealerCard: Int
    @State private var feedbackMessage: String
    
    init() {
        playerCard1 = 1
        playerCard2 = Utility.randomCard()
        dealerCard = Utility.randomCard()
        feedbackMessage = ""
    }
    var body: some View {
        VStack(spacing: 16) {
            Text(feedbackMessage)
            HStack {
                Text("Dealer: ")
                CardView(card: dealerCard)
            }
            HStack {
                Text("You: ")
                CardView(card: playerCard1)
                CardView(card: playerCard2)
            }
            
            VStack(spacing: 16) {
                HStack{
                    ActionButton(title: "Stand", backgroundColor: Color.blue, action: {
                        let (action, reason) = Utility.getActionOnSoftCount(playerCard1: playerCard1, playerCard2: playerCard2, dealerCard: dealerCard);
                        feedbackMessage = action == Utility.Action.stand ? "Correct! \(reason)" : "Incorrect. \(reason)"
                    })
                    ActionButton(title: "Hit", backgroundColor: Color.blue, action: {
                        let (action, reason) = Utility.getActionOnSoftCount(playerCard1: playerCard1, playerCard2: playerCard2, dealerCard: dealerCard);
                        feedbackMessage = action == Utility.Action.hit ? "Correct! \(reason)" : "Incorrect. \(reason)"
                    })
                }
                HStack(spacing: 16) {
                    ActionButton(title: "Split", backgroundColor: Color.blue, action: {
                        let (action, reason) = Utility.getActionOnSoftCount(playerCard1: playerCard1, playerCard2: playerCard2, dealerCard: dealerCard);
                        feedbackMessage = action == Utility.Action.split ? "Correct! \(reason)" : "Incorrect. \(reason)"
                    })
                    ActionButton(title: "Double", backgroundColor: Color.blue, action: {
                        let (action, reason) = Utility.getActionOnSoftCount(playerCard1: playerCard1, playerCard2: playerCard2, dealerCard: dealerCard);
                        feedbackMessage = action == Utility.Action.double ? "Correct! \(reason)" : "Incorrect. \(reason)"
                    })
                }
                
            }

            ActionButton(title: "Deal", backgroundColor: Color.green, action: {
                playerCard1 = 1
                playerCard2 = Utility.randomCard()
                dealerCard = Utility.randomCard()
                feedbackMessage = ""
            })
        }
        .padding()
    }
}


struct SoftCountsView_Previews: PreviewProvider {
    static var previews: some View {
        SoftCountsView().preferredColorScheme(.dark)
    }
}

To refactor the SoftCountsView, you can consider the following steps:

Extract the buttons into a separate view to make the code more modular and easier to read.

Use a ForEach loop for creating the action buttons to avoid repetition.

Create a function to handle the button actions and update the feedbackMessage.

Here's the refactored code:

import SwiftUI

struct SoftCountsView: View {
    @State private var playerCard1: Int
    @State private var playerCard2: Int
    @State private var dealerCard: Int
    @State private var feedbackMessage: String
    
    init() {
        playerCard1 = 1
        playerCard2 = Utility.randomCard()
        dealerCard = Utility.randomCard()
        feedbackMessage = ""
    }
    
    var body: some View {
        VStack(spacing: 16) {
            Text(feedbackMessage)
            HStack {
                Text("Dealer: ")
                CardView(card: dealerCard)
            }
            HStack {
                Text("You: ")
                CardView(card: playerCard1)
                CardView(card: playerCard2)
            }
            
            ActionButtonsView(playerCard1: $playerCard1, playerCard2: $playerCard2, dealerCard: $dealerCard, feedbackMessage: $feedbackMessage)
            
            ActionButton(title: "Deal", backgroundColor: Color.green, action: {
                playerCard1 = 1
                playerCard2 = Utility.randomCard()
                dealerCard = Utility.randomCard()
                feedbackMessage = ""
            })
        }
        .padding()
    }
}

struct ActionButtonsView: View {
    @Binding var playerCard1: Int
    @Binding var playerCard2: Int
    @Binding var dealerCard: Int
    @Binding var feedbackMessage: String
    
    let actions: [Utility.Action] = [.stand, .hit, .split, .double]
    
    var body: some View {
        VStack(spacing: 16) {
            ForEach(0..<actions.count / 2) { index in
                HStack {
                    ForEach(0..<2) { subIndex in
                        let action = actions[index * 2 + subIndex]
                        ActionButton(title: action.rawValue.capitalized, backgroundColor: Color.blue, action: {
                            handleAction(action: action)
                        })
                    }
                }
            }
        }
    }
    
    private func handleAction(action: Utility.Action) {
        let (correctAction, reason) = Utility.getActionOnSoftCount(playerCard1: playerCard1, playerCard2: playerCard2, dealerCard: dealerCard)
        feedbackMessage = action == correctAction ? "Correct! \(reason)" : "Incorrect. \(reason)"
    }
}

struct SoftCountsView_Previews: PreviewProvider {
    static var previews: some View {
        SoftCountsView().preferredColorScheme(.dark)
    }
}

This refactoring makes the code more maintainable and easier to understand.

User: Non-constant range: argument must be an integer literal

To resolve the "Non-constant range: argument must be an integer literal" issue, you can use the Array initializer with a range instead of using ForEach directly with a range. Here's the updated ActionButtonsView:

struct ActionButtonsView: View {
    @Binding var playerCard1: Int
    @Binding var playerCard2: Int
    @Binding var dealerCard: Int
    @Binding var feedbackMessage: String
    
    let actions: [Utility.Action] = [.stand, .hit, .split, .double]
    
    var body: some View {
        VStack(spacing: 16) {
            ForEach(Array(0..<(actions.count / 2)), id: \.self) { index in
                HStack {
                    ForEach(Array(0..<2), id: \.self) { subIndex in
                        let action = actions[index * 2 + subIndex]
                        ActionButton(title: action.rawValue.capitalized, backgroundColor: Color.blue, action: {
                            handleAction(action: action)
                        })
                    }
                }
            }
        }
    }
    
    private func handleAction(action: Utility.Action) {
        let (correctAction, reason) = Utility.getActionOnSoftCount(playerCard1: playerCard1, playerCard2: playerCard2, dealerCard: dealerCard)
        feedbackMessage = action == correctAction ? "Correct! \(reason)" : "Incorrect. \(reason)"
    }
}

By using Array(0..<(actions.count / 2)) and Array(0..<2), you create arrays with the desired ranges, which can be used in the ForEach loops without causing the "Non-constant range: argument must be an integer literal" error.

User: Make the background red when the player makes a mistake

To make the background red when the player makes a mistake, you can add a Color property to the SoftCountsView and update it based on the feedbackMessage. Here's the updated code:

import SwiftUI

struct SoftCountsView: View {
    @State private var playerCard1: Int
    @State private var playerCard2: Int
    @State private var dealerCard: Int
    @State private var feedbackMessage: String
    @State private var backgroundColor: Color
    
    init() {
        playerCard1 = 1
        playerCard2 = Utility.randomCard()
        dealerCard = Utility.randomCard()
        feedbackMessage = ""
        backgroundColor = .clear
    }
    
    var body: some View {
        VStack(spacing: 16) {
            Text(feedbackMessage)
            HStack {
                Text("Dealer: ")
                CardView(card: dealerCard)
            }
            HStack {
                Text("You: ")
                CardView(card: playerCard1)
                CardView(card: playerCard2)
            }
            
            ActionButtonsView(playerCard1: $playerCard1, playerCard2: $playerCard2, dealerCard: $dealerCard, feedbackMessage: $feedbackMessage, backgroundColor: $backgroundColor)
            
            ActionButton(title: "Deal", backgroundColor: Color.green, action: {
                playerCard1 = 1
                playerCard2 = Utility.randomCard()
                dealerCard = Utility.randomCard()
                feedbackMessage = ""
                backgroundColor = .clear
            })
        }
        .padding()
        .background(backgroundColor)
    }
}

struct ActionButtonsView: View {
    @Binding var playerCard1: Int
    @Binding var playerCard2: Int
    @Binding var dealerCard: Int
    @Binding var feedbackMessage: String
    @Binding var backgroundColor: Color
    
    let actions: [Utility.Action] = [.stand, .hit, .split, .double]
    
    var body: some View {
        VStack(spacing: 16) {
            ForEach(Array(0..<(actions.count / 2)), id: \.self) { index in
                HStack {
                    ForEach(Array(0..<2), id: \.self) { subIndex in
                        let action = actions[index * 2 + subIndex]
                        ActionButton(title: action.rawValue.capitalized, backgroundColor: Color.blue, action: {
                            handleAction(action: action)
                        })
                    }
                }
            }
        }
    }
    
    private func handleAction(action: Utility.Action) {
        let (correctAction, reason) = Utility.getActionOnSoftCount(playerCard1: playerCard1, playerCard2: playerCard2, dealerCard: dealerCard)
        if action == correctAction {
            feedbackMessage = "Correct! \(reason)"
            backgroundColor = .clear
        } else {
            feedbackMessage = "Incorrect. \(reason)"
            backgroundColor = .red
        }
    }
}

struct SoftCountsView_Previews: PreviewProvider {
    static var previews: some View {
        SoftCountsView().preferredColorScheme(.dark)
    }
}

Now, when the player makes a mistake, the background will turn red. It will reset to its original color when the "Deal" button is pressed.

Great, now this sister view needs to be modified accordingly.


//
//  SplitsView.swift
//  BlackJackPractice
//
//  Created by Victor Quach on 3/22/23.
//

import SwiftUI


struct SplitsView: View {
    @State private var playerCard1: Int
    @State private var playerCard2: Int
    @State private var dealerCard: Int
    @State private var feedbackMessage: String
    
    init() {
        // Generate new random cards for the player and dealer
        let card = Utility.randomCard()
        playerCard1 = card
        playerCard2 = card
        dealerCard = Utility.randomCard()

        // Clear the feedback message
        feedbackMessage = ""
    }
    
    var body: some View {
        VStack(spacing: 16) {
            Text(feedbackMessage)
            HStack {
                Text("Dealer: ")
                CardView(card: dealerCard)
            }
            HStack {
                Text("You: ")
                CardView(card: playerCard1)
                CardView(card: playerCard2)
            }

            HStack {
                ActionButton(title: "Split", backgroundColor: Color.blue, action: {
                    let (shouldSplit, reason) = Utility.shouldSplitCards(playerCard1: playerCard1, playerCard2: playerCard2, dealerCard: dealerCard)
                    feedbackMessage = shouldSplit ? "Correct! \(reason)" : "Incorrect. \(reason)"
                })
                ActionButton(title: "Don't Split", backgroundColor: Color.blue, action: {
                    let (shouldSplit, reason) = Utility.shouldSplitCards(playerCard1: playerCard1, playerCard2: playerCard2, dealerCard: dealerCard)
                    feedbackMessage = !shouldSplit ? "Correct! \(reason)" : "Incorrect. \(reason)"
                })
            }
            ActionButton(title: "Deal", backgroundColor: Color.green, action: {
                playerCard1 = Utility.randomCard()
                playerCard2 = playerCard1
                dealerCard = Utility.randomCard()
                feedbackMessage = ""
            })
        }
        .padding()
    }
}


struct SplitsView_Previews: PreviewProvider {
    static var previews: some View {
        SplitsView().preferredColorScheme(.dark)
    }
}

To modify the SplitsView accordingly, you can follow the same approach as in the SoftCountsView refactoring. Extract the buttons into a separate view, use a ForEach loop for creating the action buttons, and update the background color based on the player's mistake. Here's the updated code:

import SwiftUI

struct SplitsView: View {
    @State private var playerCard1: Int
    @State private var playerCard2: Int
    @State private var dealerCard: Int
    @State private var feedbackMessage: String
    @State private var backgroundColor: Color
    
    init() {
        let card = Utility.randomCard()
        playerCard1 = card
        playerCard2 = card
        dealerCard = Utility.randomCard()
        feedbackMessage = ""
        backgroundColor = .clear
    }
    
    var body: some View {
        VStack(spacing: 16) {
            Text(feedbackMessage)
            HStack {
                Text("Dealer: ")
                CardView(card: dealerCard)
            }
            HStack {
                Text("You: ")
                CardView(card: playerCard1)
                CardView(card: playerCard2)
            }

            SplitButtonsView(playerCard1: $playerCard1, playerCard2: $playerCard2, dealerCard: $dealerCard, feedbackMessage: $feedbackMessage, backgroundColor: $backgroundColor)

            ActionButton(title: "Deal", backgroundColor: Color.green, action: {
                let newCard = Utility.randomCard()
                playerCard1 = newCard
                playerCard2 = newCard
                dealerCard = Utility.randomCard()
                feedbackMessage = ""
                backgroundColor = .clear
            })
        }
        .padding()
        .background(backgroundColor)
    }
}

struct SplitButtonsView: View {
    @Binding var playerCard1: Int
    @Binding var playerCard2: Int
    @Binding var dealerCard: Int
    @Binding var feedbackMessage: String
    @Binding var backgroundColor: Color
    
    let actions: [String] = ["Split", "Don't Split"]
    
    var body: some View {
        HStack {
            ForEach(actions, id: \.self) { action in
                ActionButton(title: action, backgroundColor: Color.blue, action: {
                    handleAction(action: action)
                })
            }
        }
    }
    
    private func handleAction(action: String) {
        let (shouldSplit, reason) = Utility.shouldSplitCards(playerCard1: playerCard1, playerCard2: playerCard2, dealerCard: dealerCard)
        if (action == "Split" && shouldSplit) || (action == "Don't Split" && !shouldSplit) {
            feedbackMessage = "Correct! \(reason)"
            backgroundColor = .clear
        } else {
            feedbackMessage = "Incorrect. \(reason)"
            backgroundColor = .red
        }
    }
}

struct SplitsView_Previews: PreviewProvider {
    static var previews: some View {
        SplitsView().preferredColorScheme(.dark)
    }
}

Now, the SplitsView has been refactored to include the same improvements as the SoftCountsView, making the code more maintainable and easier to understand. The background color will also change to red when the player makes a mistake and reset to its original color when the "Deal" button is pressed.