UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

Day 25 Solution

Forums > 100 Days of SwiftUI

Here's my solution for the challenge.

Would love to hear your feedback!

struct scoreText: View {
    var text: String

    var body: some View {
        Text(text)
            .font(.largeTitle.bold())
    }

    init(_ text: String) {
        self.text = text
    }
}

struct instructionsText: View {
    var text: String

    var body: some View {
        Text(text)
            .foregroundStyle(.secondary)
            .font(.subheadline.weight(.heavy))
    }

    init(_ text: String) {
        self.text = text
    }
}

struct ContentView: View {
    let moves = ["rock", "paper", "scissors"]
    let movesIcons = ["🪨", "📄", "✂️"]
    @State private var score = 0
    @State private var rounds = 0
    @State private var playerMove = 0
    @State private var appMove = Int.random(in: 0...2)
    @State private var playerShouldWin = Bool.random()
    @State private var gameOver = false

    func newMatch() {
        // to not repeat the same app move
        let oldAppMove = appMove
        while oldAppMove == appMove{
            appMove = Int.random(in: 0...2)
        }

        playerShouldWin.toggle()
    }

    func resetGame() {
        score = 0
        rounds = 0
        appMove = Int.random(in: 0...2)
        playerShouldWin = Bool.random()
    }

    func moveMade() {
        // cases if the player should win
        if moves[appMove] == "rock" && moves[playerMove] == "paper" {
            score += playerShouldWin ? 1 : -1
        } else if moves[appMove] == "paper" && moves[playerMove] == "scissors" {
            score += playerShouldWin ? 1 : -1
        } else if moves[appMove] == "scissors" && moves[playerMove] == "rock" {
            score += playerShouldWin ? 1 : -1

        // cases if the player should lose
        } else if moves[appMove] == "rock" && moves[playerMove] == "scissors" {
            score += playerShouldWin ? -1 : 1
        } else if moves[appMove] == "paper" && moves[playerMove] == "rock" {
            score += playerShouldWin ? -1 : 1
        } else if moves[appMove] == "scissors" && moves[playerMove] == "paper" {
            score += playerShouldWin ? -1 : 1

        // cases if the player makes a draw move (choosing the same move counts as a negative)
        } else {
            score -= 1
        }

        // keeping the score not below zero
        score = score < 0 ? 0 : score

        rounds += 1

        if rounds == 10 {
            gameOver = true
        } else {
            newMatch()
        }
    }

    var body: some View {
        ZStack {
            LinearGradient(gradient: Gradient(colors: [
                Color(red: 0.6, green: 0.3, blue: 0.15),
                Color(red: 0.35, green: 0.2, blue: 0.1)
            ]), startPoint: .top, endPoint: .bottom)
            .ignoresSafeArea()

            VStack {
                Spacer()

                scoreText("Score: \(score)")

                Spacer()

                VStack(spacing: 25) {
                    HStack(spacing: 4) {
                        instructionsText("\(playerShouldWin ? "Win" : "Lose")")
                            .foregroundColor(playerShouldWin ? .green : .red)

                        instructionsText("against \(moves[appMove])")
                    }

                    Text(movesIcons[appMove])
                        .font(.system(size: 80))
                }

                Spacer()

                HStack(spacing: 40) {
                    ForEach(0..<3) { move in
                        Button {
                            playerMove = move
                            moveMade()
                        } label: {
                            Text(movesIcons[move])
                                .font(.system(size: 80))
                                .rotationEffect(.degrees(move == 2 ? 180 : 0)) // to only flip scissors emoji
                        }
                    }
                }

                Spacer()
            }.foregroundColor(.white)
        }.alert("Game Over", isPresented: $gameOver) {
            Button("Reset game") {
                resetGame()
            }
        } message: {
            Text("Your final score is \(score)/10")
        }
    }
}

2      

Does it work ✅ Great job. Just a couple of things!

The naming convention is to start struct/classs with a Captial letter

struct InstructionsText

Genaral put properties first then body and then methods at the end

You made two structs for instructionsText and scoreText when you could just add Text with modifliers as they are simple structs

EG

scoreText("Score: \(score)")

becomes

Text("Score: \(score)"
  .font(.largeTitle.bold())

You could look at ViewModifier if you want the same style though out the app Check out Custom modifiers

2      

This is for a bit more adavanced Make a file (I called GamePanelView)

struct GamePanelView: View {
    let playerShouldWin: Bool
    let appIcon: String
    let appText: String

    var body: some View {
        VStack(spacing: 25) {
            HStack(spacing: 4) {
                Text("\(playerShouldWin ? "Win" : "Lose")")
                    .foregroundStyle(.secondary)
                    .font(.subheadline.weight(.heavy))
                    .foregroundColor(playerShouldWin ? .green : .red)

                Text("against \(appText)")
                    .foregroundStyle(.secondary)
                    .font(.subheadline.weight(.heavy))
            }

            Text(appIcon)
                .font(.system(size: 80))
        }
    }
}

then use it in ContentView

GamePanelView(playerShouldWin: playerShouldWin, appIcon: movesIcons[appMove], appText: moves[appMove])

Also the buttons (this is little more tricky) as have function had to change the method to func moveMade(_ playerMove: Int) {

struct OptionButtonView: View {
    let movesIcons: [String]
    let moveMade: (Int) -> Void

    var body: some View {
        HStack(spacing: 40) {
            ForEach(0..<3) { move in
                Button {
                    moveMade(move)
                } label: {
                    Text(movesIcons[move])
                        .font(.system(size: 80))
                        .rotationEffect(.degrees(move == 2 ? 180 : 0)) // to only flip scissors emoji
                }
            }
        }
    }
}

and use in ContentView

OptionButtonView(movesIcons: movesIcons, moveMade: moveMade)

Next is to get the logic out of the View with a ContentViewModel

import Foundation

class ContentViewModel: ObservableObject {
    let moves = ["rock", "paper", "scissors"]
    let movesIcons = ["🪨", "📄", "✂️"]
    @Published var score = 0
    @Published var rounds = 0
    @Published var appMove = Int.random(in: 0...2)
    @Published var playerShouldWin = Bool.random()
    @Published var gameOver = false

    func newMatch() {
        // to not repeat the same app move
        let oldAppMove = appMove
        while oldAppMove == appMove{
            appMove = Int.random(in: 0...2)
        }

        playerShouldWin.toggle()
    }

    func resetGame() {
        score = 0
        rounds = 0
        appMove = Int.random(in: 0...2)
        playerShouldWin = Bool.random()
    }

    func moveMade(_ playerMove: Int) {
        // cases if the player should win
        if moves[appMove] == "rock" && moves[playerMove] == "paper" {
            score += playerShouldWin ? 1 : -1
        } else if moves[appMove] == "paper" && moves[playerMove] == "scissors" {
            score += playerShouldWin ? 1 : -1
        } else if moves[appMove] == "scissors" && moves[playerMove] == "rock" {
            score += playerShouldWin ? 1 : -1

            // cases if the player should lose
        } else if moves[appMove] == "rock" && moves[playerMove] == "scissors" {
            score += playerShouldWin ? -1 : 1
        } else if moves[appMove] == "paper" && moves[playerMove] == "rock" {
            score += playerShouldWin ? -1 : 1
        } else if moves[appMove] == "scissors" && moves[playerMove] == "paper" {
            score += playerShouldWin ? -1 : 1

            // cases if the player makes a draw move (choosing the same move counts as a negative)
        } else {
            score -= 1
        }

        // keeping the score not below zero
        score = score < 0 ? 0 : score

        rounds += 1

        if rounds == 10 {
            gameOver = true
        } else {
            newMatch()
        }
    }
}

Now the ContentView looks like this

struct ContentView: View {
    @StateObject var vm = ContentViewModel() // <- access the ViewModel

    var body: some View {
        ZStack {
            LinearGradient(gradient: Gradient(colors: [
                Color(red: 0.6, green: 0.3, blue: 0.15),
                Color(red: 0.35, green: 0.2, blue: 0.1)
            ]), startPoint: .top, endPoint: .bottom)
            .ignoresSafeArea()

            VStack {
                Spacer()

                Text("Score: \(vm.score)")
                    .font(.largeTitle.bold())

                Spacer()

                GamePanelView(playerShouldWin: vm.playerShouldWin,
                              appIcon: vm.movesIcons[vm.appMove],
                              appText: vm.moves[vm.appMove]
                )

                Spacer()

                OptionButtonView(movesIcons: vm.movesIcons, moveMade: vm.moveMade)

                Spacer()
            }.foregroundColor(.white)
        }.alert("Game Over", isPresented: $vm.gameOver) {
            Button("Reset game") {
                vm.resetGame()
            }
        } message: {
            Text("Your final score is \(vm.score)/10")
        }
    }
}

Still the same but only code in View is for views and little better to read

2      

TAKE YOUR SKILLS TO THE NEXT LEVEL If you like Hacking with Swift, you'll love Hacking with Swift+ – it's my premium service where you can learn advanced Swift and SwiftUI, functional programming, algorithms, and more. Plus it comes with stacks of benefits, including monthly live streams, downloadable projects, a 20% discount on all books, and free gifts!

Find out more

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

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

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.