Files
breakpilot-lehrer/breakpilot-drive/UnityScripts/BreakpilotAPI.cs
Benjamin Boenisch 5a31f52310 Initial commit: breakpilot-lehrer - Lehrer KI Platform
Services: Admin-Lehrer, Backend-Lehrer, Studio v2, Website,
Klausur-Service, School-Service, Voice-Service, Geo-Service,
BreakPilot Drive, Agent-Core

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 23:47:26 +01:00

600 lines
20 KiB
C#

// ==============================================
// 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<QuizQuestion> cachedQuestions = new List<QuizQuestion>();
private List<VisualTrigger> cachedTriggers = new List<VisualTrigger>();
// 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<LearningLevel> onSuccess, Action<string> 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<LearningLevel>(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<GameDifficulty> onSuccess, Action<string> 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<GameDifficulty>(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<QuizQuestion[]> onSuccess = null,
Action<string> 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<QuizQuestionList>(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<VisualTrigger[]> onSuccess, Action<string> 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<VisualTriggersWrapper>(wrappedJson);
cachedTriggers = new List<VisualTrigger>(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<SessionResponse> onSuccess = null, Action<string> 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<SessionResponse>(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<GameSession> offlineSessions = new List<GameSession>();
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<GameSession>(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<QuizQuestion> 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<ProgressResponse> onSuccess,
Action<string> 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<ProgressResponse>(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<LeaderboardEntry[]> onSuccess,
Action<string> 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<LeaderboardWrapper>(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<UserStats> onSuccess,
Action<string> 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<UserStats>(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<bool, string> 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);
}
}
}
}
}