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