// ============================================== // BreakpilotAPI.cs - Unity API Client // ============================================== // Kopiere diese Datei nach Assets/Scripts/Network/ // um mit dem Breakpilot Backend zu kommunizieren. using System; using System.Collections; using System.Collections.Generic; using System.Threading.Tasks; using UnityEngine; using UnityEngine.Networking; namespace BreakpilotDrive { [Serializable] public class LearningLevel { public string user_id; public int overall_level; public float math_level; public float german_level; public float english_level; } [Serializable] public class GameDifficulty { public float lane_speed; public float obstacle_frequency; public float power_up_chance; public int question_complexity; public int answer_time; public bool hints_enabled; public float speech_speed; } [Serializable] public class QuizQuestion { public string id; public string question_text; public string audio_url; public string[] options; public int correct_index; public int difficulty; public string subject; public int grade_level; public string quiz_mode; // "quick" oder "pause" public string visual_trigger; // z.B. "bridge" public float time_limit_seconds; } [Serializable] public class QuizQuestionList { public QuizQuestion[] questions; } [Serializable] public class GameSession { public string user_id; public string game_mode; // "video" oder "audio" public int duration_seconds; public float distance_traveled; public int score; public int questions_answered; public int questions_correct; public int difficulty_level; } [Serializable] public class SessionResponse { public string session_id; public string status; public int? new_level; } [Serializable] public class VisualTrigger { public string trigger; public int question_count; public int[] difficulties; public string[] subjects; } [Serializable] public class ProgressData { public string date; public int sessions; public int total_score; public int questions; public int correct; public float accuracy; public float avg_difficulty; } [Serializable] public class ProgressResponse { public string user_id; public int days; public int data_points; public ProgressData[] progress; } [Serializable] public class LeaderboardEntry { public int rank; public string user_id; public int total_score; public string display_name; } public class BreakpilotAPI : MonoBehaviour { // Singleton Pattern public static BreakpilotAPI Instance { get; private set; } [Header("API Konfiguration")] [SerializeField] private string baseUrl = "http://localhost:8000/api/game"; [SerializeField] private bool useOfflineCache = true; // Cached Data private LearningLevel cachedLevel; private GameDifficulty cachedDifficulty; private List cachedQuestions = new List(); private List cachedTriggers = new List(); // API URL property (for other scripts) public string BaseUrl => baseUrl; void Awake() { if (Instance == null) { Instance = this; DontDestroyOnLoad(gameObject); } else { Destroy(gameObject); } } // ============================================== // Helper: Add Auth Header to Request // ============================================== private void AddAuthHeader(UnityWebRequest request) { if (AuthManager.Instance != null) { string authHeader = AuthManager.Instance.GetAuthHeader(); if (!string.IsNullOrEmpty(authHeader)) { request.SetRequestHeader("Authorization", authHeader); } } } // Get current user ID (from auth or fallback) public string GetCurrentUserId() { if (AuthManager.Instance != null && AuthManager.Instance.IsAuthenticated) { return AuthManager.Instance.UserId; } return "anonymous"; } // ============================================== // Lernniveau abrufen // ============================================== public IEnumerator GetLearningLevel(string userId, Action onSuccess, Action onError = null) { string url = $"{baseUrl}/learning-level/{userId}"; using (UnityWebRequest request = UnityWebRequest.Get(url)) { yield return request.SendWebRequest(); if (request.result == UnityWebRequest.Result.Success) { LearningLevel level = JsonUtility.FromJson(request.downloadHandler.text); cachedLevel = level; onSuccess?.Invoke(level); } else { Debug.LogWarning($"API Fehler: {request.error}"); // Offline-Fallback if (useOfflineCache && cachedLevel != null) { onSuccess?.Invoke(cachedLevel); } else { // Standard-Level onSuccess?.Invoke(new LearningLevel { user_id = userId, overall_level = 3 }); } onError?.Invoke(request.error); } } } // ============================================== // Schwierigkeit abrufen // ============================================== public IEnumerator GetDifficulty(int level, Action onSuccess, Action onError = null) { string url = $"{baseUrl}/difficulty/{level}"; using (UnityWebRequest request = UnityWebRequest.Get(url)) { yield return request.SendWebRequest(); if (request.result == UnityWebRequest.Result.Success) { GameDifficulty difficulty = JsonUtility.FromJson(request.downloadHandler.text); cachedDifficulty = difficulty; onSuccess?.Invoke(difficulty); } else { Debug.LogWarning($"API Fehler: {request.error}"); // Fallback: Default-Schwierigkeit if (cachedDifficulty != null) { onSuccess?.Invoke(cachedDifficulty); } else { onSuccess?.Invoke(GetDefaultDifficulty(level)); } onError?.Invoke(request.error); } } } // ============================================== // Quiz-Fragen abrufen // ============================================== public IEnumerator GetQuizQuestions( int difficulty, int count, string mode = null, // "quick", "pause", oder null fuer beide string subject = null, Action onSuccess = null, Action onError = null) { string url = $"{baseUrl}/quiz/questions?difficulty={difficulty}&count={count}"; if (!string.IsNullOrEmpty(mode)) url += $"&mode={mode}"; if (!string.IsNullOrEmpty(subject)) url += $"&subject={subject}"; using (UnityWebRequest request = UnityWebRequest.Get(url)) { yield return request.SendWebRequest(); if (request.result == UnityWebRequest.Result.Success) { // Unity's JsonUtility braucht Wrapper fuer Arrays string wrappedJson = "{\"questions\":" + request.downloadHandler.text + "}"; QuizQuestionList list = JsonUtility.FromJson(wrappedJson); // Cache updaten if (list.questions != null) { cachedQuestions.AddRange(list.questions); } onSuccess?.Invoke(list.questions); } else { Debug.LogWarning($"API Fehler: {request.error}"); // Offline-Fallback if (useOfflineCache && cachedQuestions.Count > 0) { var filtered = cachedQuestions.FindAll(q => Math.Abs(q.difficulty - difficulty) <= 1 && (string.IsNullOrEmpty(mode) || q.quiz_mode == mode) ); onSuccess?.Invoke(filtered.GetRange(0, Math.Min(count, filtered.Count)).ToArray()); } onError?.Invoke(request.error); } } } // ============================================== // Visuelle Trigger abrufen // ============================================== public IEnumerator GetVisualTriggers(Action onSuccess, Action onError = null) { string url = $"{baseUrl}/quiz/visual-triggers"; using (UnityWebRequest request = UnityWebRequest.Get(url)) { yield return request.SendWebRequest(); if (request.result == UnityWebRequest.Result.Success) { // Parse Array string wrappedJson = "{\"triggers\":" + request.downloadHandler.text + "}"; VisualTriggersWrapper wrapper = JsonUtility.FromJson(wrappedJson); cachedTriggers = new List(wrapper.triggers); onSuccess?.Invoke(wrapper.triggers); } else { Debug.LogWarning($"API Fehler: {request.error}"); onError?.Invoke(request.error); } } } [Serializable] private class VisualTriggersWrapper { public VisualTrigger[] triggers; } // ============================================== // Spielsession speichern // ============================================== public IEnumerator SaveGameSession(GameSession session, Action onSuccess = null, Action onError = null) { string url = $"{baseUrl}/session"; string json = JsonUtility.ToJson(session); using (UnityWebRequest request = new UnityWebRequest(url, "POST")) { byte[] bodyRaw = System.Text.Encoding.UTF8.GetBytes(json); request.uploadHandler = new UploadHandlerRaw(bodyRaw); request.downloadHandler = new DownloadHandlerBuffer(); request.SetRequestHeader("Content-Type", "application/json"); yield return request.SendWebRequest(); if (request.result == UnityWebRequest.Result.Success) { SessionResponse response = JsonUtility.FromJson(request.downloadHandler.text); onSuccess?.Invoke(response); // Bei Level-Aenderung cachedLevel updaten if (response.new_level.HasValue && cachedLevel != null) { cachedLevel.overall_level = response.new_level.Value; } } else { Debug.LogWarning($"Session speichern fehlgeschlagen: {request.error}"); // Offline: Spaeter synchronisieren if (useOfflineCache) { QueueOfflineSession(session); } onError?.Invoke(request.error); } } } // ============================================== // Offline-Handling // ============================================== private List offlineSessions = new List(); private void QueueOfflineSession(GameSession session) { offlineSessions.Add(session); // Spaeter: PlayerPrefs oder lokale DB nutzen Debug.Log($"Session zur Offline-Queue hinzugefuegt. Queue-Groesse: {offlineSessions.Count}"); } public IEnumerator SyncOfflineSessions() { var sessionsToSync = new List(offlineSessions); offlineSessions.Clear(); foreach (var session in sessionsToSync) { yield return SaveGameSession(session, onSuccess: (response) => Debug.Log($"Offline-Session synchronisiert: {response.session_id}"), onError: (error) => offlineSessions.Add(session) // Zurueck in Queue ); } } // ============================================== // Helper // ============================================== private GameDifficulty GetDefaultDifficulty(int level) { // Fallback-Werte wenn API nicht erreichbar return new GameDifficulty { lane_speed = 3f + level, obstacle_frequency = 0.3f + (level * 0.1f), power_up_chance = 0.4f - (level * 0.05f), question_complexity = level, answer_time = 15 - (level * 2), hints_enabled = level <= 3, speech_speed = 0.8f + (level * 0.1f) }; } // ============================================== // Frage fuer visuellen Trigger holen // ============================================== public QuizQuestion GetQuestionForTrigger(string triggerType, int difficulty) { var matching = cachedQuestions.FindAll(q => q.quiz_mode == "quick" && q.visual_trigger == triggerType && Math.Abs(q.difficulty - difficulty) <= 1 ); if (matching.Count > 0) { return matching[UnityEngine.Random.Range(0, matching.Count)]; } return null; } // ============================================== // Cache-Zugriff // ============================================== public LearningLevel GetCachedLevel() { return cachedLevel; } public List GetCachedQuestions() { return cachedQuestions; } public GameDifficulty GetCachedDifficulty() { return cachedDifficulty; } public int GetCachedQuestionsCount() { return cachedQuestions.Count; } public void ClearQuestionCache() { cachedQuestions.Clear(); } // ============================================== // Progress API (Phase 5) // ============================================== public IEnumerator GetProgress( string userId, int days, Action onSuccess, Action onError = null) { string url = $"{baseUrl}/progress/{userId}?days={days}"; using (UnityWebRequest request = UnityWebRequest.Get(url)) { AddAuthHeader(request); yield return request.SendWebRequest(); if (request.result == UnityWebRequest.Result.Success) { var response = JsonUtility.FromJson(request.downloadHandler.text); onSuccess?.Invoke(response); } else { Debug.LogWarning($"Progress API error: {request.error}"); onError?.Invoke(request.error); } } } // ============================================== // Leaderboard API (Phase 5) // ============================================== public IEnumerator GetLeaderboard( string timeframe, int limit, bool anonymize, Action onSuccess, Action onError = null) { string url = $"{baseUrl}/leaderboard/display?timeframe={timeframe}&limit={limit}&anonymize={anonymize.ToString().ToLower()}"; using (UnityWebRequest request = UnityWebRequest.Get(url)) { yield return request.SendWebRequest(); if (request.result == UnityWebRequest.Result.Success) { string wrappedJson = "{\"entries\":" + request.downloadHandler.text + "}"; var wrapper = JsonUtility.FromJson(wrappedJson); onSuccess?.Invoke(wrapper.entries); } else { Debug.LogWarning($"Leaderboard API error: {request.error}"); onError?.Invoke(request.error); } } } [Serializable] private class LeaderboardWrapper { public LeaderboardEntry[] entries; } // ============================================== // Stats API // ============================================== public IEnumerator GetUserStats( string userId, Action onSuccess, Action onError = null) { string url = $"{baseUrl}/stats/{userId}"; using (UnityWebRequest request = UnityWebRequest.Get(url)) { AddAuthHeader(request); yield return request.SendWebRequest(); if (request.result == UnityWebRequest.Result.Success) { var stats = JsonUtility.FromJson(request.downloadHandler.text); onSuccess?.Invoke(stats); } else { Debug.LogWarning($"Stats API error: {request.error}"); onError?.Invoke(request.error); } } } [Serializable] public class UserStats { public string user_id; public int overall_level; public float math_level; public float german_level; public float english_level; public int total_play_time_minutes; public int total_sessions; public int questions_answered; public int questions_correct; public float accuracy; } // ============================================== // Health Check // ============================================== public IEnumerator HealthCheck(Action onComplete) { string url = $"{baseUrl}/health"; using (UnityWebRequest request = UnityWebRequest.Get(url)) { request.timeout = 5; yield return request.SendWebRequest(); if (request.result == UnityWebRequest.Result.Success) { onComplete?.Invoke(true, request.downloadHandler.text); } else { onComplete?.Invoke(false, request.error); } } } } }