// // WordPuzzleViewB.swift // AIGrammar // // Created by oscar on 2025/7/16. // import AVFoundation var soundID: SystemSoundID = 1104 // 系统键盘点击声 func playKeyClickSound() { AudioServicesPlaySystemSound(soundID) } import SwiftUI struct WordPuzzleView: View { @StateObject private var viewModel = WordPuzzleViewModel() var body: some View { ZStack { Color.pink.opacity(0.2).edgesIgnoringSafeArea(.all) VStack(spacing: 16) { // Top: score & timer Text("Find the Word!") .font(.largeTitle) .bold() .padding(.bottom, 30) HStack { Text("Wins: \(viewModel.dailyWins)") .font(.headline) Spacer() Text("Words: \(viewModel.guessedWords)/4") .font(.headline) Spacer() Text("Time: \(viewModel.timeRemaining)s") .font(.headline) } .padding(.horizontal, 30) .padding(.bottom, 20) // Grid GridView(letters: viewModel.letters, selectedPositions: $viewModel.selectedPositions) Spacer() // Toast toastView .frame(height: 50) .frame(maxWidth: .infinity) //.background(viewModel.showToast ? viewModel.toastColor : Color.pink.opacity(0.0)) .cornerRadius(8) .padding(.bottom, 10) HStack { Button("Shuffle") { viewModel.shuffle() } .padding() .background(Color.orange.opacity(0.7)) .foregroundColor(.white) .cornerRadius(10) .font(.subheadline) // 调整字体大小为标题大小 Spacer() Button("Submit") { viewModel.submit() } .padding() .background(Color.green.opacity(0.7)) .foregroundColor(.white) .cornerRadius(10) .font(.subheadline) // 调整字体大小为标题大小 } .padding(.horizontal) .padding(.bottom, 20) } .padding() .onAppear { viewModel.startTimer() } .onDisappear { viewModel.stopTimer() } } } var toastView: some View { HStack { if viewModel.showToast { if viewModel.toastFlag { Image(systemName: "checkmark.circle.fill") .foregroundColor(.green) } else { Image(systemName: "xmark.octagon.fill") .foregroundColor(.red) } Text(viewModel.toastMessage) .font(.body) } } .opacity(viewModel.showToast ? 1 : 0) .animation(.easeInOut, value: viewModel.showToast) } } // MARK: - GridView struct GridView: View { let letters: [[String]] @Binding var selectedPositions: [(row: Int, col: Int)] var body: some View { GeometryReader { geo in let size = geo.size.width * 0.8 VStack(spacing: 2) { ForEach(0.. = ["WORD", "HAVE", "SWIFT", "CODE"] // 示例字典 private var wordList: [String] = [] init() { loadWordList() generateNewPuzzle() } /// 加载本地字典 private func loadWordList() { if let url = Bundle.main.url(forResource: "wordlist", withExtension: "txt") { do { let content = try String(contentsOf: url) wordList = content .components(separatedBy: .newlines) .map { $0.trimmingCharacters(in: .whitespacesAndNewlines).uppercased() } .filter { !$0.isEmpty } } catch { logger.warning("Failed to load word list: \(error)") } } else { logger.warning("wordlist.txt not found!") } logger.info("load wordlist. total words: \(wordList.count)") } /// 生成新的题目 func generateNewPuzzle() { if wordList.isEmpty { logger.warning("Word list is empty") wordList = ["WORD", "HAVE", "SWIFT", "CODE"] // 给个示例的 } var chosenWords: Set = [] var uniqueLetters: Set = [] while (uniqueLetters.count < 16 && chosenWords.count < 6) || chosenWords.count < 4 { if let word = wordList.randomElement() { chosenWords.insert(word) uniqueLetters.formUnion(word) } } allWords = chosenWords // 确保 uniqueLetters ≥ 16 var selectedLetters = Array(uniqueLetters) let alphabet = Array("ABCDEFGHIJKLMNOPQRSTUVWXYZ") while selectedLetters.count < 16 { let randomLetter = alphabet.randomElement()! if !selectedLetters.contains(randomLetter) { selectedLetters.append(randomLetter) } } // 打乱 let shuffledLetters = selectedLetters.shuffled() letters = Array(repeating: Array(repeating: "", count: 4), count: 4) for i in 0..<4 { for j in 0..<4 { letters[i][j] = String(shuffledLetters[i * 4 + j]) } } guessedWords = 0 selectedPositions.removeAll() timeRemaining = 60 //shuffleCount = 0 logger.info("New puzzle generated with words: \(allWords) and letters: \(shuffledLetters)") } func startTimer() { stopTimer() timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in if self.timeRemaining > 0 { self.timeRemaining -= 1 } else { self.stopTimer() } } } func stopTimer() { timer?.invalidate() timer = nil } func submit() { let word = selectedWord().uppercased() selectedPositions.removeAll() guard !word.isEmpty else { return } if timeRemaining <= 0 { showToast(message: "Time's up!", succ: false) return } if allWords.contains(word) { guessedWords += 1 showToast(message: "Correct!", succ: true) } else { showToast(message: "Please try again", succ: false) } if guessedWords >= 4 { //stopTimer() dailyWins += 1 showToast(message: "Congratulations! \nStart next puzzle...", succ: true) generateNewPuzzle() // 新开一局 } } func shuffle() { if shuffleCount >= 10 { showToast(message: "No more shuffles today", succ: false) return } shuffleCount += 1 generateNewPuzzle() } private func selectedWord() -> String { var result = "" for pos in selectedPositions { result += letters[pos.row][pos.col] } return result } private func showToast(message: String, succ: Bool) { toastMessage = message toastFlag = succ showToast = true DispatchQueue.main.asyncAfter(deadline: .now() + 3) { self.showToast = false } } } struct WordPuzzleView_Previews: PreviewProvider { static var previews: some View { WordPuzzleView() } } #Preview { WordPuzzleView() }