Ok so after several days of wracking my brain, I will admit to myself that I need help. My app seems to have some strange behaviour I can't quite figure out.
The code seems to work well until I call the 'restart' function using the button, to generate a new rootWord. The restart function clears the usedWords array, and clears most user scores (except 'high score'), then calls the 'startGame' function.
All looks well until you play the game. If you have entered say 3 or 4 words into the game (which populate the usedWords array), and then restart the game with a button press to generate the new rootWord, it seems that any new words submitted thereafter do not show in the list view...until you have entered a suffciient quantity of words which exceeds the previous array size. This is weird, as I have (I think) cleared the contents of the usedWords array, and new words are appended at 0. Further weirdness is that I can submit the same valid word repeatedly in the restarted games only, which seems to go through the 'guard' statements and correctly get added to the userScore, but they do NOT appear in my list view until, again, the word is submitted more times than the previous array's last size. Finally, I notice that repeatedly submittng the same word over and over (in a restarted game only) will increment the score correctly, but does not reject the repeated word which has already been submitted, until it starts to appear in my list on screen. It's like the words somehow aren't getting to the usedWords array, until I have submitted several words, but ONLY when playing a restarted game! I feel like I'm going mad.
I'm sure I am just being an idiot, but I cant figure out where this has gone wrong. Like I say, the game functions perfectly until I call restart to generate a new rootWord...so strange. Any help would be appreciated, but I'm also keen to understand how I can go through a debugging process to track my data and see what is 'inside' the array.
//
// ContentView.swift
// WordScramble
//
// Created by David on 19/4/2023.
//
import SwiftUI
struct ContentView: View {
let backgroundGradient = LinearGradient(
colors: [Color.red, Color.blue],
startPoint: .top, endPoint: .bottom)
@State private var usedWords = [String]()
@State private var rootWord = ""
@State private var newWord = ""
@State private var errorTitle = ""
@State private var errorMessage = ""
@State private var showingError = false
@State private var totalScore = 0
@State private var wordCount = 0
@State private var wordLengthScore = 0
@State private var highScore = 0
var body: some View {
ZStack {
NavigationView {
List {
Section {
TextField("Enter your word", text: $newWord)
.autocapitalization(.none)
}
Section {
ForEach(usedWords, id: \.self) { word in
HStack {
Text(word)
Image(systemName: "\(word.count).circle.fill")
}
}
}
}
.background( .pink)
.scrollContentBackground(.hidden)
.navigationTitle(rootWord)
.toolbar {
Button("New Word", action: restart)
}
.onSubmit(addNewWord)
.onAppear(perform: startGame)
.alert(errorTitle, isPresented: $showingError) {
Button("OK", role: .cancel) { }
} message: {
Text(errorMessage)
}
}
VStack {
Spacer()
HStack{
Text("Word score: \(wordCount)")
.padding(20)
Text("Length Score: \(wordLengthScore)")
}
Text("Total Score: \(totalScore)")
.padding()
Text("HIGH SCORE: \(highScore)")
}
.font(.headline)
}
}
func addNewWord() {
let answer = newWord.lowercased().trimmingCharacters(in: .whitespacesAndNewlines)
guard answer.count > 0 else { return }
guard isOriginal(word: answer) else {
wordError(title: "Word used already", message: "Be more original")
return
}
guard isPossible(word: answer) else {
wordError(title: "Word not possible", message: "You can't spell that word from '\(rootWord)'!")
return
}
guard isReal(word: answer) else {
wordError(title: "Word not recognized", message: "You can't just make them up, you know!")
return
}
guard isNotRoot(word: answer) else {
wordError(title: "Nice try", message: "That's the original word!")
return
}
guard longEnough(word: answer) else {
wordError(title: "Too short", message: "A word needs to be 3 or more letters!")
return
}
wordCount += 1
wordLengthScore += answer.count
totalScore += wordLengthScore + wordCount
if totalScore > highScore { highScore = totalScore } else { return }
withAnimation {
usedWords.insert(answer, at: 0)
}
newWord = ""
}
func startGame() {
if let startWordsURL = Bundle.main.url(forResource: "start", withExtension: "txt") {
if let startWords = try? String(contentsOf: startWordsURL) {
let allWords = startWords.components(separatedBy: "\n")
rootWord = allWords.randomElement() ?? "silkworm"
return
}
}
fatalError("Fatal Error: Could not load start.txt from bundle")
}
func isOriginal(word: String) -> Bool {
return !usedWords.contains(newWord)
}
func isPossible(word: String) -> Bool {
var tempWord = rootWord
for letter in word {
if let pos = tempWord.firstIndex(of: letter) {
tempWord.remove(at: pos)
} else {
return false
}
}
return true
}
func isReal(word: String) -> Bool {
let checker = UITextChecker()
let range = NSRange(location: 0, length: word.utf16.count)
let misspelledRange = checker.rangeOfMisspelledWord(in: word, range: range, startingAt: 0, wrap: false, language: "en")
return misspelledRange.location == NSNotFound
}
func isNotRoot(word: String) -> Bool {
return !(word == rootWord)
}
func longEnough(word: String) -> Bool {
return word.count >= 3
}
func restart() {
usedWords = []
totalScore = 0
wordCount = 0
wordLengthScore = 0
newWord = ""
startGame()
}
func wordError(title: String, message: String) {
errorTitle = title
errorMessage = message
showingError = true
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}