//
// ContentView.swift
// CowsAndBulls
//
// Created by pat on 11/20/22.
//
import SwiftUI
struct ContentView: View {
var body: some View {
VStack(spacing: 0) {
HStack {
TextField("Enter a guess…", text: $guess)
.onSubmit(submitGuess)
Button("Go", action: submitGuess)
}
.padding()
List(0..<guesses.count, id: \.self) { index in
let guess = guesses[index]
let shouldShowResult = (enableHardMode == false) || (enableHardMode && index == 0)
HStack {
Text(guess)
Spacer()
if shouldShowResult {
let res = result(for: guess)
if res.first == "0" {
Text(result(for: guess))
.foregroundColor(.red)
} else if res.first == "4" {
Text(result(for: guess))
.foregroundColor(.green)
} else {
Text(result(for: guess))
.foregroundColor(.yellow)
}
}
}
}
.listStyle(.sidebar)
if showGuessCount {
Text("Guesses: \(guesses.count)/\(maximumGuesses)")
.padding()
}
}
.frame(width: 250)
.frame(minHeight: 300)
.navigationTitle("Cows and Bulls")
.onAppear(perform: startNewGame)
.onChange(of: answerLength) { _ in startNewGame() }
.alert("You win!", isPresented: $isGameOver) {
Button("Play Again", action: startNewGame)
} message: {
if guesses.count < 10 {
Text("Congratulations!\n Score: \(guesses.count)")
} else if guesses.count < 20 {
Text("Almost sweaty.\n Score: \(guesses.count)")
} else {
Text("Get güd nüb!\n Score: \(guesses.count)")
}
}
.alert("You lose!", isPresented: $isGameLost) {
Button ("Play Again", action: startNewGame)
} message: {
Text("You are a big chungus. Have you tried sucking less?\n\nThe correct answer is \(answer).")
}
// Removed .touchBar modifier.
}
@State private var guesses: [String] = []
@State private var guess = ""
@State private var answer = ""
@State private var isGameOver = false
@State private var isGameLost = false
@AppStorage("maximumGuesses") var maximumGuesses = 100
@AppStorage("answerLength") private var answerLength = 4
@AppStorage("enableHardMode") var enableHardMode = false
@AppStorage("showGuessCount") var showGuessCount = false
func submitGuess() {
// 1. Validate current guess.
// 1.1 all chars are unique
guard !Set(guesses).contains(guess) else { return }
guard Set(guess).count == answerLength else { return }
// 1.2 length is 4
guard guess.count == answerLength else { return }
// 1.3 all chars are in 0...9
let badCharacters = CharacterSet(charactersIn: "0123456789").inverted
guard guess.rangeOfCharacter(from: badCharacters) == nil else { return }
// 2. Add current guess to guesses array
guesses.insert(guess, at: 0)
// 3. If guess is answer, show "You win!" dialogue
if result(for: guess).contains("\(answerLength)b") {
isGameOver = true
}
if guess == answer {
// Present a dialogue, popup, or some message.
} else if guesses.count == maximumGuesses {
isGameLost = true
}
// 4. Reset value in guess
guess = ""
}
func result(for guess: String) -> String {
// 1. Initialize bulls and cows to 0
var bulls: Int = 0
var cows: Int = 0
// 2. Convert guess and answer to arrays
let guessLetters = Array(guess)
let answerLetters = Array(answer)
// 3. Iterate across the array(guess)
// 3.1) if guess item is answer item: bulls++
// 3.2) else if guess item is in answer: cows++
for (index, letter) in guessLetters.enumerated() {
if letter == answerLetters[index] {
bulls += 1
} else if answerLetters.contains(letter) {
cows += 1
}
}
// 4. Return bulls and cows as "part of a string"(?).
return "\(bulls)b \(cows)c"
}
func startNewGame() {
guard answerLength >= 3 && answerLength <= 8 else { return }
guess = ""
guesses.removeAll() // `keepingCapacity: Bool` parameter maintains array capacity even while emptying it.
answer = ""
let shuffledNumbers: [Int] = (0...9).shuffled()
for i in 0..<answerLength {
answer.append(String(shuffledNumbers[i])) // Strings are mutable in Swift??
}
}
}
struct SettingsView: View {
var body: some View {
TabView {
Form {
TextField("Maximum guesses", value: $maximumGuesses, format: .number)
.help("The maximum number of answers you can submit. Changing this will immediately restart your game.")
TextField ("Answer length", value: $answerLength, format: .number)
.help("The length of the number string to guess. Changing this will immediately restart your game.")
if answerLength < 3 || answerLength > 8 {
Text("Must be between 3 and 8")
.foregroundColor(.red)
}
}
.padding()
.tabItem {
Label("Game", systemImage: "number.circle")
}
Form {
Toggle("Enable hard mode", isOn: $enableHardMode)
.help("Hard mode shows only the result for the most recent guess.")
Toggle("Show guess count", isOn: $showGuessCount)
.help("Adds a footer below your guesses showing how many guessses you have made.")
}
.padding()
.tabItem {
Label("Advanced", systemImage: "gearshape.2")
}
}
.frame(width: 400)
}
@AppStorage("maximumGuesses") var maximumGuesses = 100
@AppStorage("answerLength") private var answerLength = 4
@AppStorage("enableHardMode") var enableHardMode = false
@AppStorage("showGuessCount") var showGuessCount = false
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}