feat: replace Leaderboard with Highscore
This commit is contained in:
@@ -91,4 +91,26 @@ class ViewModel: ObservableObject {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func highscore(for category: String) -> Int {
|
||||||
|
let dict = UserDefaults.standard.dictionary(forKey: "categoryHighscores") as? [String: Int] ?? [:]
|
||||||
|
return dict[category] ?? 0
|
||||||
|
}
|
||||||
|
|
||||||
|
@discardableResult
|
||||||
|
func updateHighscoreIfNeeded() -> Bool {
|
||||||
|
guard let category = selectedCategory else { return false }
|
||||||
|
var dict = UserDefaults.standard.dictionary(forKey: "categoryHighscores") as? [String: Int] ?? [:]
|
||||||
|
let oldScore = dict[category] ?? 0
|
||||||
|
if score > oldScore {
|
||||||
|
dict[category] = score
|
||||||
|
UserDefaults.standard.set(dict, forKey: "categoryHighscores")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func allHighscores() -> [String: Int] {
|
||||||
|
UserDefaults.standard.dictionary(forKey: "categoryHighscores") as? [String: Int] ?? [:]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,11 +10,7 @@ import SwiftUI
|
|||||||
struct CategorySelectionView: View {
|
struct CategorySelectionView: View {
|
||||||
@StateObject private var viewModel = ViewModel()
|
@StateObject private var viewModel = ViewModel()
|
||||||
@AppStorage("globalScore") var globalScore: Int = 0
|
@AppStorage("globalScore") var globalScore: Int = 0
|
||||||
@State private var boardData: [[String: Any]] = []
|
|
||||||
|
|
||||||
private func loadBoardData() {
|
|
||||||
boardData = UserDefaults.standard.array(forKey: "leaderboard") as? [[String: Any]] ?? []
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
@@ -78,9 +74,14 @@ struct CategorySelectionView: View {
|
|||||||
.foregroundColor(.blue)
|
.foregroundColor(.blue)
|
||||||
.font(.title2)
|
.font(.title2)
|
||||||
|
|
||||||
Text(category)
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
.font(.headline)
|
Text(category)
|
||||||
.foregroundColor(.primary)
|
.font(.headline)
|
||||||
|
.foregroundColor(.primary)
|
||||||
|
Text("Highscore: \(viewModel.highscore(for: category))")
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
@@ -103,58 +104,11 @@ struct CategorySelectionView: View {
|
|||||||
}
|
}
|
||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
|
|
||||||
// Scoreboard
|
|
||||||
Text("Leaderboard")
|
|
||||||
.font(.title2)
|
|
||||||
.fontWeight(.semibold)
|
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
|
||||||
.padding(.horizontal)
|
|
||||||
if !boardData.isEmpty {
|
|
||||||
VStack(spacing: 8) {
|
|
||||||
ForEach(Array(boardData.prefix(5).enumerated()), id: \.offset) { index, scoreData in
|
|
||||||
HStack {
|
|
||||||
Text("\(index + 1).")
|
|
||||||
.font(.headline)
|
|
||||||
.foregroundColor(index < 3 ? .orange : .primary)
|
|
||||||
.frame(width: 25, alignment: .leading)
|
|
||||||
|
|
||||||
Text(scoreData["name"] as? String ?? "Unbekannt")
|
|
||||||
.font(.body)
|
|
||||||
.foregroundColor(.primary)
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
Text("\(scoreData["score"] as? Int ?? 0) Punkte")
|
|
||||||
.font(.body)
|
|
||||||
.fontWeight(.medium)
|
|
||||||
.foregroundColor(.blue)
|
|
||||||
}
|
|
||||||
.padding(.horizontal, 16)
|
|
||||||
.padding(.vertical, 8)
|
|
||||||
.background(
|
|
||||||
RoundedRectangle(cornerRadius: 12)
|
|
||||||
.fill(Color(.systemGray6))
|
|
||||||
.overlay(
|
|
||||||
RoundedRectangle(cornerRadius: 12)
|
|
||||||
.stroke(Color(.systemGray4), lineWidth: 1)
|
|
||||||
)
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding(.horizontal)
|
|
||||||
} else {
|
|
||||||
Text("Keine Leadboard Daten...")
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
.background(Color.white)
|
.background(Color.white)
|
||||||
.navigationBarHidden(true)
|
.navigationBarHidden(true)
|
||||||
.navigationTitle("Quiz-Auswahl")
|
.navigationTitle("Quiz-Auswahl")
|
||||||
.onAppear {
|
|
||||||
loadBoardData()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,41 +10,36 @@ import SwiftUI
|
|||||||
struct QuizFinishedView: View {
|
struct QuizFinishedView: View {
|
||||||
let score: Int
|
let score: Int
|
||||||
let total: Int
|
let total: Int
|
||||||
@Binding var playerName: String
|
let isNewHighscore: Bool
|
||||||
let onPlayAgain: () -> Void
|
let onPlayAgain: () -> Void
|
||||||
let onBackToCategories: () -> Void
|
let onBackToCategories: () -> Void
|
||||||
let onSaveScore: () -> Void
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 20) {
|
VStack(spacing: 20) {
|
||||||
Text("🎉 Quiz beendet!")
|
Text("🎉 Quiz beendet!")
|
||||||
.font(.title)
|
.font(.title)
|
||||||
|
|
||||||
Text("Dein Ergebnis: \(score) von \(total) Punkten")
|
Text("Dein Ergebnis: \(score) von \(total) Punkten")
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
|
|
||||||
|
if isNewHighscore {
|
||||||
|
Text("🔥 Neuer Highscore!")
|
||||||
|
.font(.headline)
|
||||||
|
.foregroundColor(.orange)
|
||||||
|
}
|
||||||
|
|
||||||
Button("Nochmal spielen", action: onPlayAgain)
|
Button("Nochmal spielen", action: onPlayAgain)
|
||||||
.padding()
|
.padding()
|
||||||
.background(Color.blue)
|
.background(Color.blue)
|
||||||
.foregroundColor(.white)
|
.foregroundColor(.white)
|
||||||
.cornerRadius(10)
|
.cornerRadius(10)
|
||||||
|
|
||||||
Button("Zurück zur Kategorieauswahl", action: onBackToCategories)
|
Button("Zurück zur Kategorieauswahl", action: onBackToCategories)
|
||||||
.padding()
|
.padding()
|
||||||
.background(Color.gray)
|
.background(Color.gray)
|
||||||
.foregroundColor(.white)
|
.foregroundColor(.white)
|
||||||
.cornerRadius(10)
|
.cornerRadius(10)
|
||||||
|
|
||||||
TextField("Dein Name:", text: $playerName)
|
|
||||||
.textFieldStyle(RoundedBorderTextFieldStyle())
|
|
||||||
.padding(.horizontal)
|
|
||||||
|
|
||||||
Button("Score speichern", action: onSaveScore)
|
|
||||||
.padding()
|
|
||||||
.background(playerName.isEmpty ? Color.gray : Color.green)
|
|
||||||
.foregroundColor(.white)
|
|
||||||
.cornerRadius(10)
|
|
||||||
.disabled(playerName.isEmpty)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,8 +19,7 @@ struct QuizView: View {
|
|||||||
@State private var selectedAnswerIndex: Int? = nil
|
@State private var selectedAnswerIndex: Int? = nil
|
||||||
@State private var isAnswered = false
|
@State private var isAnswered = false
|
||||||
@State private var showExitAlert = false
|
@State private var showExitAlert = false
|
||||||
@State private var playerName: String = ""
|
@State private var isNewHighscore = false
|
||||||
@State private var scoreIsSaved = false
|
|
||||||
|
|
||||||
@State private var estimationValue: Double = 0
|
@State private var estimationValue: Double = 0
|
||||||
@State private var estimationSubmitted: Bool = false
|
@State private var estimationSubmitted: Bool = false
|
||||||
@@ -32,17 +31,15 @@ struct QuizView: View {
|
|||||||
QuizFinishedView(
|
QuizFinishedView(
|
||||||
score: viewModel.score,
|
score: viewModel.score,
|
||||||
total: viewModel.questions.count,
|
total: viewModel.questions.count,
|
||||||
playerName: $playerName,
|
isNewHighscore: isNewHighscore,
|
||||||
onPlayAgain: { restartQuiz() },
|
onPlayAgain: { restartQuiz() },
|
||||||
onBackToCategories: { dismiss() },
|
onBackToCategories: { dismiss() }
|
||||||
onSaveScore: {
|
|
||||||
saveScore()
|
|
||||||
dismiss()
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
.onAppear {
|
.onAppear {
|
||||||
AudioServicesPlaySystemSound(1322)
|
AudioServicesPlaySystemSound(1322)
|
||||||
score += viewModel.score
|
score += viewModel.score
|
||||||
|
// Highscore prüfen/setzen
|
||||||
|
isNewHighscore = viewModel.updateHighscoreIfNeeded()
|
||||||
}
|
}
|
||||||
.onDisappear {
|
.onDisappear {
|
||||||
viewModel.isQuizFinished = false
|
viewModel.isQuizFinished = false
|
||||||
@@ -131,10 +128,7 @@ struct QuizView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
// Fragen für die Kategorie
|
viewModel.loadQuestions(for: category)
|
||||||
if viewModel.selectedCategory != category {
|
|
||||||
viewModel.loadQuestions(for: category)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.navigationBarBackButtonHidden(!viewModel.isQuizFinished)
|
.navigationBarBackButtonHidden(!viewModel.isQuizFinished)
|
||||||
.toolbar {
|
.toolbar {
|
||||||
@@ -206,37 +200,6 @@ struct QuizView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func saveScore() {
|
|
||||||
// Bestehende Scores laden
|
|
||||||
var savedScores = UserDefaults.standard.array(forKey: "leaderboard") as? [[String: Any]] ?? []
|
|
||||||
|
|
||||||
// Neuen Score hinzufügen
|
|
||||||
let newScore: [String: Any] = [
|
|
||||||
"name": playerName,
|
|
||||||
"score": viewModel.score,
|
|
||||||
"date": Date()
|
|
||||||
]
|
|
||||||
|
|
||||||
savedScores.append(newScore)
|
|
||||||
|
|
||||||
// Nach Score sortieren
|
|
||||||
savedScores.sort { score1, score2 in
|
|
||||||
let score1Value = score1["score"] as? Int ?? 0
|
|
||||||
let score2Value = score2["score"] as? Int ?? 0
|
|
||||||
return score1Value > score2Value
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nur die besten 10 Scores behalten
|
|
||||||
if savedScores.count > 5 {
|
|
||||||
savedScores = Array(savedScores.prefix(10))
|
|
||||||
}
|
|
||||||
|
|
||||||
UserDefaults.standard.set(savedScores, forKey: "leaderboard")
|
|
||||||
scoreIsSaved = true
|
|
||||||
|
|
||||||
print("Score gespeichert: \(playerName) - \(viewModel.score) Punkte")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#Preview {
|
#Preview {
|
||||||
|
|||||||
Reference in New Issue
Block a user