modify files
This commit is contained in:
@ -28,6 +28,9 @@
|
||||
555027392C81C0ED00A05441 /* QCloudTTS.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 555027382C81C0ED00A05441 /* QCloudTTS.xcframework */; };
|
||||
5550273C2C8322F800A05441 /* PushHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5550273B2C8322F800A05441 /* PushHandler.swift */; };
|
||||
5586E0882C80AD2D00026733 /* TTSManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5586E0872C80AD2D00026733 /* TTSManager.swift */; };
|
||||
558DB7A32E27A91A004D6ADB /* WordPuzzleGameView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 558DB7A22E27A91A004D6ADB /* WordPuzzleGameView.swift */; };
|
||||
558DB7A52E27B049004D6ADB /* WordPuzzleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 558DB7A42E27B049004D6ADB /* WordPuzzleView.swift */; };
|
||||
558DB7B22E2A78A5004D6ADB /* wordlist.txt in Resources */ = {isa = PBXBuildFile; fileRef = 558DB7B12E2A78A5004D6ADB /* wordlist.txt */; };
|
||||
559E6D7C2C34EAE700C971B9 /* IapManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 559E6D7B2C34EAE700C971B9 /* IapManager.swift */; };
|
||||
559E6D7E2C35355200C971B9 /* LogManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 559E6D7D2C35355200C971B9 /* LogManager.swift */; };
|
||||
55A954A22BBBFD0C00BF181E /* GrammarData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55A954A12BBBFD0C00BF181E /* GrammarData.swift */; };
|
||||
@ -96,6 +99,9 @@
|
||||
5550273B2C8322F800A05441 /* PushHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushHandler.swift; sourceTree = "<group>"; };
|
||||
5586E0832C8092C400026733 /* AIGrammar-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "AIGrammar-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
5586E0872C80AD2D00026733 /* TTSManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TTSManager.swift; sourceTree = "<group>"; };
|
||||
558DB7A22E27A91A004D6ADB /* WordPuzzleGameView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WordPuzzleGameView.swift; sourceTree = "<group>"; };
|
||||
558DB7A42E27B049004D6ADB /* WordPuzzleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WordPuzzleView.swift; sourceTree = "<group>"; };
|
||||
558DB7B12E2A78A5004D6ADB /* wordlist.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = wordlist.txt; sourceTree = "<group>"; };
|
||||
559E6D7B2C34EAE700C971B9 /* IapManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IapManager.swift; sourceTree = "<group>"; };
|
||||
559E6D7D2C35355200C971B9 /* LogManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogManager.swift; sourceTree = "<group>"; };
|
||||
55A954A12BBBFD0C00BF181E /* GrammarData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GrammarData.swift; sourceTree = "<group>"; };
|
||||
@ -170,6 +176,7 @@
|
||||
5500A38D2BB3C7E80065A1D3 /* Products */,
|
||||
B164443BE53434F82C385E52 /* Pods */,
|
||||
40FEAE98949AC44A40547B20 /* Frameworks */,
|
||||
558DB7A72E292893004D6ADB /* Resource */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@ -186,6 +193,7 @@
|
||||
5500A38E2BB3C7E80065A1D3 /* AIGrammar */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
558DB7B12E2A78A5004D6ADB /* wordlist.txt */,
|
||||
5550273A2C8311CB00A05441 /* Info.plist */,
|
||||
5586E0842C8093DF00026733 /* third-party */,
|
||||
5586E0832C8092C400026733 /* AIGrammar-Bridging-Header.h */,
|
||||
@ -239,6 +247,8 @@
|
||||
5500A3C52BB40AD30065A1D3 /* TranslateView.swift */,
|
||||
5500A3C72BB40ADE0065A1D3 /* SettingsView.swift */,
|
||||
55BC47502C3D431300120A7D /* IAPView.swift */,
|
||||
558DB7A22E27A91A004D6ADB /* WordPuzzleGameView.swift */,
|
||||
558DB7A42E27B049004D6ADB /* WordPuzzleView.swift */,
|
||||
);
|
||||
path = View;
|
||||
sourceTree = "<group>";
|
||||
@ -275,6 +285,13 @@
|
||||
path = "third-party";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
558DB7A72E292893004D6ADB /* Resource */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
);
|
||||
path = Resource;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
55BC47472C3A380C00120A7D /* CommView */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -426,6 +443,7 @@
|
||||
files = (
|
||||
5500A3972BB3C7EB0065A1D3 /* Preview Assets.xcassets in Resources */,
|
||||
5500A3942BB3C7EB0065A1D3 /* Assets.xcassets in Resources */,
|
||||
558DB7B22E2A78A5004D6ADB /* wordlist.txt in Resources */,
|
||||
551C8C342C79946700B1A88C /* GoogleService-Info.plist in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@ -555,6 +573,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5500A3992BB3C7EB0065A1D3 /* Persistence.swift in Sources */,
|
||||
558DB7A52E27B049004D6ADB /* WordPuzzleView.swift in Sources */,
|
||||
55DAC6572BBA984B00BDD4C8 /* InputView.swift in Sources */,
|
||||
55A954A22BBBFD0C00BF181E /* GrammarData.swift in Sources */,
|
||||
55BB12792BBD4C9900D2BEA4 /* RichText.swift in Sources */,
|
||||
@ -562,6 +581,7 @@
|
||||
55BC47512C3D431300120A7D /* IAPView.swift in Sources */,
|
||||
550B85A22C2BC624008834E5 /* InitAPP.swift in Sources */,
|
||||
5500A3C42BB40AC40065A1D3 /* WordsView.swift in Sources */,
|
||||
558DB7A32E27A91A004D6ADB /* WordPuzzleGameView.swift in Sources */,
|
||||
5509CEF12BB54DD10056C5C2 /* Config.swift in Sources */,
|
||||
55BB127B2BBD653100D2BEA4 /* ShareSheet.swift in Sources */,
|
||||
5550273C2C8322F800A05441 /* PushHandler.swift in Sources */,
|
||||
|
||||
Binary file not shown.
@ -37,12 +37,19 @@ struct AllTabView: View {
|
||||
}
|
||||
.tag(2)
|
||||
|
||||
WordPuzzleView()
|
||||
.tabItem {
|
||||
Image(systemName: "timer.circle")
|
||||
Text("Puzzel")
|
||||
}
|
||||
.tag(3)
|
||||
|
||||
SettingsView()
|
||||
.tabItem {
|
||||
Image(systemName: "gear")
|
||||
Text("Settings")
|
||||
}
|
||||
.tag(3)
|
||||
.tag(4)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
7
AIGrammar/View/Untitled.swift
Normal file
7
AIGrammar/View/Untitled.swift
Normal file
@ -0,0 +1,7 @@
|
||||
//
|
||||
// Untitled.swift
|
||||
// AIGrammar
|
||||
//
|
||||
// Created by oscar on 2025/7/16.
|
||||
//
|
||||
|
||||
153
AIGrammar/View/WordPuzzleGameView.swift
Normal file
153
AIGrammar/View/WordPuzzleGameView.swift
Normal file
@ -0,0 +1,153 @@
|
||||
//
|
||||
// WordPuzzleView.swift
|
||||
// AIGrammar
|
||||
//
|
||||
// Created by oscar on 2025/7/16.
|
||||
//
|
||||
import SwiftUI
|
||||
import AVFoundation
|
||||
|
||||
struct WordPuzzleGameView: View {
|
||||
@State private var letters: [[String]] = []
|
||||
@State private var selectedLetters: [(Int, Int)] = []
|
||||
@State private var foundWords: [String] = []
|
||||
@State private var score: Int = 0
|
||||
@State private var remainingTime = 60
|
||||
@State private var isGameOver = false
|
||||
|
||||
@GestureState private var dragLocation: CGPoint? = nil
|
||||
@State private var audioPlayer: AVAudioPlayer?
|
||||
|
||||
let validWords = ["WORD", "GAME", "PUZZLE", "MET", "ME", "GO"]
|
||||
let gridSize = 4
|
||||
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
|
||||
|
||||
init() {
|
||||
var chars = Array("WORDPUZZLEGAMETO").map { String($0) }
|
||||
chars.shuffle()
|
||||
_letters = State(initialValue: stride(from: 0, to: chars.count, by: gridSize).map {
|
||||
Array(chars[$0..<$0+gridSize])
|
||||
})
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Text("Time: \(remainingTime)s Score: \(score)")
|
||||
.font(.headline)
|
||||
|
||||
if isGameOver {
|
||||
Text("Game Over!")
|
||||
.font(.largeTitle)
|
||||
.foregroundColor(.red)
|
||||
} else {
|
||||
VStack(spacing: 4) {
|
||||
ForEach(0..<gridSize, id: \.self) { row in
|
||||
HStack(spacing: 4) {
|
||||
ForEach(0..<gridSize, id: \.self) { col in
|
||||
Text(letters[row][col])
|
||||
.font(.title)
|
||||
.frame(width: 50, height: 50)
|
||||
.background(
|
||||
selectedLetters.contains(where: { $0 == (row, col) }) ?
|
||||
Color.blue.opacity(0.7) : Color.gray.opacity(0.2)
|
||||
)
|
||||
.cornerRadius(8)
|
||||
.onTapGesture {
|
||||
handleSelect(row: row, col: col)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.gesture(
|
||||
DragGesture()
|
||||
.updating($dragLocation) { value, state, _ in
|
||||
state = value.location
|
||||
selectLetter(at: value.location)
|
||||
}
|
||||
.onEnded { _ in
|
||||
submitWord()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
ScrollView(.horizontal) {
|
||||
HStack {
|
||||
ForEach(foundWords, id: \.self) { word in
|
||||
Text(word)
|
||||
.padding(6)
|
||||
.background(Color.green.opacity(0.3))
|
||||
.cornerRadius(6)
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.onReceive(timer) { _ in
|
||||
guard !isGameOver else { return }
|
||||
if remainingTime > 0 {
|
||||
remainingTime -= 1
|
||||
} else {
|
||||
isGameOver = true
|
||||
timer.upstream.connect().cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 点击选择
|
||||
private func handleSelect(row: Int, col: Int) {
|
||||
if !selectedLetters.contains(where: { $0 == (row, col) }) {
|
||||
selectedLetters.append((row, col))
|
||||
}
|
||||
}
|
||||
|
||||
/// 拖动选择
|
||||
private func selectLetter(at point: CGPoint?) {
|
||||
guard let point = point else { return }
|
||||
for row in 0..<gridSize {
|
||||
for col in 0..<gridSize {
|
||||
let cellSize: CGFloat = 50 + 4
|
||||
let cellOrigin = CGPoint(x: CGFloat(col) * cellSize + 20,
|
||||
y: CGFloat(row) * cellSize + 120)
|
||||
let cellRect = CGRect(origin: cellOrigin, size: CGSize(width: 50, height: 50))
|
||||
if cellRect.contains(point) {
|
||||
if !selectedLetters.contains(where: { $0 == (row, col) }) {
|
||||
selectedLetters.append((row, col))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 提交选中的单词
|
||||
private func submitWord() {
|
||||
let word = selectedLetters.map { letters[$0.0][$0.1] }.joined()
|
||||
|
||||
if validWords.contains(word) && !foundWords.contains(word) {
|
||||
foundWords.append(word)
|
||||
score += word.count * 10
|
||||
playSound(named: "success")
|
||||
} else {
|
||||
playSound(named: "fail")
|
||||
}
|
||||
selectedLetters.removeAll()
|
||||
}
|
||||
|
||||
/// 播放提示音
|
||||
private func playSound(named name: String) {
|
||||
guard let url = Bundle.main.url(forResource: name, withExtension: "mp3") else { return }
|
||||
audioPlayer = try? AVAudioPlayer(contentsOf: url)
|
||||
audioPlayer?.play()
|
||||
}
|
||||
}
|
||||
|
||||
struct WordPuzzleGameView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
WordPuzzleGameView()
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
WordPuzzleGameView()
|
||||
}
|
||||
333
AIGrammar/View/WordPuzzleView.swift
Normal file
333
AIGrammar/View/WordPuzzleView.swift
Normal file
@ -0,0 +1,333 @@
|
||||
//
|
||||
// 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..<letters.count, id: \.self) { row in
|
||||
HStack(spacing: 2) {
|
||||
ForEach(0..<letters[row].count, id: \.self) { col in
|
||||
let selected = selectedPositions.contains { $0.row == row && $0.col == col }
|
||||
Text(letters[row][col])
|
||||
.frame(width: size / CGFloat(letters.count),
|
||||
height: size / CGFloat(letters.count))
|
||||
.background(
|
||||
ZStack {
|
||||
if selected {
|
||||
RoundedRectangle(cornerRadius: 6)
|
||||
.fill(Color.blue.opacity(0.7))
|
||||
} else {
|
||||
RoundedRectangle(cornerRadius: 6)
|
||||
.fill(Color.white)
|
||||
.shadow(color: .gray.opacity(0.3), radius: 2, x: 0, y: 2)
|
||||
RoundedRectangle(cornerRadius: 6)
|
||||
.stroke(Color.gray.opacity(0.3), lineWidth: 0.5)
|
||||
}
|
||||
}
|
||||
)
|
||||
.foregroundColor(selected ? .white : .black)
|
||||
.onTapGesture {
|
||||
toggleSelection(row: row, col: col)
|
||||
playKeyClickSound()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(width: size, height: size)
|
||||
.padding()
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
|
||||
}
|
||||
.frame(height: 300) // 给个固定高度
|
||||
}
|
||||
|
||||
private func toggleSelection(row: Int, col: Int) {
|
||||
if let index = selectedPositions.firstIndex(where: { $0.row == row && $0.col == col }) {
|
||||
selectedPositions.remove(at: index)
|
||||
} else {
|
||||
selectedPositions.append((row, col))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - ViewModel
|
||||
class WordPuzzleViewModel: ObservableObject {
|
||||
@Published var letters: [[String]] = []
|
||||
@Published var selectedPositions: [(row: Int, col: Int)] = []
|
||||
@Published var guessedWords: Int = 0
|
||||
@Published var dailyWins: Int = 0
|
||||
@Published var timeRemaining: Int = 60
|
||||
@Published var shuffleCount: Int = 0
|
||||
|
||||
@Published var showToast = false
|
||||
@Published var toastFlag = false
|
||||
@Published var toastMessage = ""
|
||||
@Published var toastColor = Color.green
|
||||
|
||||
private var timer: Timer?
|
||||
private var allWords: Set<String> = ["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<String> = []
|
||||
var uniqueLetters: Set<Character> = []
|
||||
|
||||
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()
|
||||
}
|
||||
2868
AIGrammar/wordlist.txt
Normal file
2868
AIGrammar/wordlist.txt
Normal file
File diff suppressed because it is too large
Load Diff
98
Resource/wordlist.txt
Normal file
98
Resource/wordlist.txt
Normal file
@ -0,0 +1,98 @@
|
||||
WORD
|
||||
GAME
|
||||
FUN
|
||||
PLAY
|
||||
SWIFT
|
||||
CODE
|
||||
TASK
|
||||
JUMP
|
||||
CLIMB
|
||||
BRAVE
|
||||
CHART
|
||||
INDEX
|
||||
QUICK
|
||||
WORLD
|
||||
FRESH
|
||||
PLANT
|
||||
LIGHT
|
||||
MOUNT
|
||||
BRISK
|
||||
CLEAN
|
||||
GLOVE
|
||||
THINK
|
||||
WASTE
|
||||
PRIZE
|
||||
SHINE
|
||||
VEX
|
||||
CROWN
|
||||
BLAZE
|
||||
SHOUT
|
||||
FLOCK
|
||||
PRINT
|
||||
GLARE
|
||||
CRISP
|
||||
DREAM
|
||||
VOLT
|
||||
BRING
|
||||
MATCH
|
||||
DANCE
|
||||
PLUCK
|
||||
GRAND
|
||||
FLAME
|
||||
STONE
|
||||
TRICK
|
||||
WHILE
|
||||
BROAD
|
||||
MARCH
|
||||
CLEFT
|
||||
SHARP
|
||||
GRIND
|
||||
VOICE
|
||||
WIND
|
||||
SPLIT
|
||||
JOKER
|
||||
TRAMP
|
||||
BLANK
|
||||
CRANE
|
||||
SPARK
|
||||
FLOAT
|
||||
THIRD
|
||||
GHOST
|
||||
TWICE
|
||||
FLOUR
|
||||
COVET
|
||||
BLUSH
|
||||
JUMPY
|
||||
HOVER
|
||||
PRANK
|
||||
STORM
|
||||
FLOCK
|
||||
NIGHT
|
||||
GRACE
|
||||
FRONT
|
||||
CLEFT
|
||||
BLOWN
|
||||
PRINT
|
||||
VOUCH
|
||||
FRESH
|
||||
CHIME
|
||||
DWARF
|
||||
SLICK
|
||||
HUMAN
|
||||
GLOVE
|
||||
THUMP
|
||||
BLACK
|
||||
FLICK
|
||||
CRUSH
|
||||
SPEND
|
||||
BRINK
|
||||
LUNCH
|
||||
MOUTH
|
||||
GRASP
|
||||
THIRD
|
||||
SPARK
|
||||
TWIRL
|
||||
QUICK
|
||||
PLANT
|
||||
FIGHT
|
||||
CRISP
|
||||
Reference in New Issue
Block a user