WWDC22 SALE: Save 50% on all my Swift books and bundles! >>

Advice for simplifying some 'brute force' coding

Forums > SwiftUI

I'm looking for some pointers on coding more efficiently.

  1. First, how can I make the following code more efficient/shorter? There's a lot of duplication, but I haven't figured out a way to successfully trim things down. I'm particularly interested in what I should be doing with the button1Pressed and button2Pressed functions, as well as the Button modifiers in the HStack portion near the bottom.
  2. You'll notice lots of print("[insert letter here]") lines, which I used for testing whether or not my code was going through the correct looping. Is there a better way to do something like that using XCode? My method was extremely tedious.
  3. Does anyone understand what this console printout means: RockPaperScissors[8521:296123] void * _Nullable NSMapGet(NSMapTable Nonnull, const void Nullable): map table argument is NULL XCode was printing this out at the start of each simulation (on the Simulator) yesterday, but it hasn't done it today, so I'm guessing it was a bug in XCode itself.
import SwiftUI

struct ContentView: View {

    @State private var possibleMoves = ["rock":"🪨", "paper":"📄", "scissors":"✂️"].shuffled()
    @State private var shouldWin = Bool.random()

    @State private var score = 0
    @State private var numberOfQuestionsAsked = 0

    @State private var result = ""
    @State private var gameOverAlert = false

    func button1Pressed() {
        if possibleMoves[0].key == "rock" {
            if shouldWin {
                result = (possibleMoves[1].key == "paper" ? "Correct" : "Incorrect")
                if result == "Correct" {
                    score += 1
                    print("a")
                } else {
                    score -= 1
                    print("b")
                }
            } else {
                result = (possibleMoves[1].key == "paper" ? "Incorrect" : "Correct")
                if result == "Correct" {
                    score += 1
                    print("a'")
                } else {
                    score -= 1
                    print("b'")
                }
            }
        } else if possibleMoves[0].key == "paper" {
            if shouldWin {
                result = possibleMoves[1].key == "scissors" ? "Correct" : "Incorrect"
                if result == "Correct" {
                    score += 1
                    print("c")
                } else {
                    score -= 1
                    print("d")
                }
            } else {
                result = possibleMoves[1].key == "scissors" ? "Incorrect" : "Correct"
                if result == "Correct" {
                    score += 1
                    print("c'")
                } else {
                    score -= 1
                    print("d'")
                }
            }
        } else if possibleMoves[0].key == "scissors" {
            if shouldWin {
                result = possibleMoves[1].key == "rock" ? "Correct" : "Incorrect"
                if result == "Correct" {
                    score += 1
                    print("e")
                } else {
                    score -= 1
                    print("f")
                }
            } else {
                result = possibleMoves[1].key == "rock" ? "Incorrect" : "Correct"
                if result == "Correct" {
                    score += 1
                    print("e'")
                } else {
                    score -= 1
                    print("f'")
                }
            }
        }

        numberOfQuestionsAsked += 1

        if numberOfQuestionsAsked == 10 {
            gameOverAlert = true
        } else {
            possibleMoves.shuffle()
            shouldWin = Bool.random()
            print("\(numberOfQuestionsAsked) B \(score)")
        }
    }

    func button2Pressed() {
        if possibleMoves[0].key == "rock" {
            if shouldWin {
                result = (possibleMoves[2].key == "paper" ? "Correct" : "Incorrect")
                if result == "Correct" {
                    score += 1
                    print("g")
                } else {
                    score -= 1
                    print("h")
                }
            } else {
                result = (possibleMoves[2].key == "paper" ? "Incorrect" : "Correct")
                if result == "Correct" {
                    score += 1
                    print("g'")
                } else {
                    score -= 1
                    print("h'")
                }
            }
        } else if possibleMoves[0].key == "paper" {
            if shouldWin {
                result = possibleMoves[2].key == "scissors" ? "Correct" : "Incorrect"
                if result == "Correct" {
                    score += 1
                    print("i")
                } else {
                    score -= 1
                    print("j")
                }
            } else {
                result = possibleMoves[2].key == "scissors" ? "Incorrect" : "Correct"
                if result == "Correct" {
                    score += 1
                    print("i'")
                } else {
                    score -= 1
                    print("j'")
                }
            }
        } else if possibleMoves[0].key == "scissors" {
            if shouldWin {
                result = possibleMoves[2].key == "rock" ? "Correct" : "Incorrect"
                if result == "Correct" {
                    score += 1
                    print("k")
                } else {
                    score -= 1
                    print("l")
                }
            } else {
                result = possibleMoves[2].key == "rock" ? "Incorrect" : "Correct"
                if result == "Correct" {
                    score += 1
                    print("k'")
                } else {
                    score -= 1
                    print("l'")
                }
            }
        }
        numberOfQuestionsAsked += 1

        if numberOfQuestionsAsked == 10 {
            gameOverAlert = true
        } else {
            possibleMoves.shuffle()
            shouldWin = Bool.random()
            print("\(numberOfQuestionsAsked) B \(score)")
        }
    }

    var body: some View {
        ZStack {
            Color.black
            VStack {
                Section {
                    if result == "Correct" {
                        Text(result)
                            .font(.largeTitle)
                            .padding(10)
                            .foregroundColor(.green)
                    } else {
                        Text(result)
                            .font(.largeTitle)
                            .padding(10)
                            .foregroundColor(.red)
                    }
                }

                Spacer()

                Spacer()

                Section {
                    Text("App selects")
                        .foregroundColor(.white)
                        .font(.system(size: 30))
                }

                Section {
                    Text(possibleMoves[0].value)
                        .accessibilityLabel(possibleMoves[0].key)
                    .modifier(ButtonForMove())

                }

                Spacer()

                Spacer()

                Section {
                    Text("Which item below lets you...")
                        .font(.system(size: 30))
                        .foregroundColor(.yellow)
                }

                Section {
                    Text((shouldWin ? "WIN" : "LOSE") + "?")
                        .font(.system(size: 50))
                        .foregroundColor(.yellow)
                }

                HStack {
                    Section {
                        Button(possibleMoves[1].value, action: button1Pressed)
                            .accessibilityLabel(possibleMoves[1].key)
                            .modifier(ButtonForMove())
                            .background(.purple)
                            .clipShape(RoundedRectangle(cornerRadius: 10))
                        Spacer()
                        Button(possibleMoves[2].value, action: button2Pressed)
                            .accessibilityLabel(possibleMoves[2].key)
                            .modifier(ButtonForMove())
                            .background(.blue)
                            .clipShape(RoundedRectangle(cornerRadius: 10))
                    }
                }

                .padding(.vertical, 20)

                .alert("Game Over", isPresented: $gameOverAlert) {
                    Button("Restart", action: resetGame)
                } message: {
                    Text("Your score is \(score).")
                }
            }
        }
    }

    func resetGame() {
        score = 0
        numberOfQuestionsAsked = 0
        possibleMoves.shuffle()
        shouldWin = Bool.random()
        result = ""
    }
}

struct ButtonForMove: ViewModifier {
    func body(content: Content) -> some View {
        content
            .font(.system(size: 60))
            .frame(maxWidth: .infinity,  maxHeight: 150, alignment: .center)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

1      

Your code tells me you come from a background where you learned procedural programming.

  1. Do this
  2. do this
  3. Then do this
  4. if problem goto step 2
  5. etc

You ask:

First, how can I make the following code more efficient/shorter?

I would ask you to think about rock objects, and paper objects and scissor objects. Simplify your code by thinking about and using objects!

Now ask yourself.

  1. what are my choices in this game?
  2. what Swift construction provides choices?

You don't have to noodle very long to realize your choices are rock, paper, and scissors. And enumerations are a great way to limit your code to only recognizing these three choices.

// Copy Paste into Playgrounds
// Ensure your program will only recognize these three choices!
enum Choice: Int {
    case rock     // auto assigned value 0
    case paper    // auto assigned value 1
    case scissors // auto assigned value 2
}

// Now it's easy to assign one of these to your user. (Perhaps via buttons in your GameView?)
let playersChoice = Choice.rock //  try Choice.spock or Choice.dynamite. Compiler will whine!

Now that you have a way to assign a Choice to your user, how do you get the iPhone to make a choice? What FUNCTION might you need to get a RANDOM choice?

You can add functions to enumerations.

// add this to the enumeration above
func random() -> Choice {
    Choice(rawValue: Int.random(in: 0...2))!
}

let iPhoneChoice = playersChoice.random()  // use the playersChoice object to generate a random iPhone choice

You have more work to do!
What functions could you add to this enum to see if the iPhone's choice BEATS the user's choice?
What functions could you add to format text messages?

let didPlayerWin = playersChoice.beats( iPhoneChoice )  // returns a bool  

1      

Note: @rooster has a complete rock, paper, scissors enum in the forum.

But you asked for help and ideas. Please try to fill out the functions on your own before taking a peep at his code.

1      

Ah, I never knew about enum's RawValue identity. That helps things.

However, I don't understand the following:

let playersChoice = Choice.rock

let iPhoneChoice = playersChoice.random()

If playersChoice is a constant, and it's already been assigned the value of "rock," how can .random() work on it?

1      

Hover your mouse over the playersChoice var and hold down the option key. It should turn into a "?" question mark, right?

Click the name. It should tell you what kind of object you're over.

It will tell you that playersChoice is an object of TYPE Choice.

One of the things that Choice offers you is a function named random(). Functions return Ints, Bools, or other things, right? What does the function random() return? (Read the function signature to yourself! Get into the habit.)

// Get into the habit of reading these out loud.
// Say, "random() is a function that takes NOTHING, 
// but it will return another fully featured Choice object"
func random() -> Choice {
    Choice(rawValue: Int.random(in: 0...2))!  // make and RETURN a brand new Choice object
}

In your application, you have a fully featured object with the name of playersChoice. It just so happens, one of the features of this object is that you can ask it to run a random() function. What does that random() function do?

You're correct! playersChoice IS constant. It DOES NOT change the playersChoice object at all. However, it does "return another fully featured Choice object" to you.

All you have to do is give that shiny new, fully featured Choice object a name! I suggested iPhoneChoice.

1      

Hacking with Swift is sponsored by Fernando Olivares

SPONSORED Fernando's book will guide you in fixing bugs in three real, open-source, downloadable apps from the App Store. Learn applied programming fundamentals by refactoring real code from published apps. Hacking with Swift readers get a $10 discount!

Read the book

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.