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>
This commit is contained in:
599
breakpilot-drive/UnityScripts/BreakpilotAPI.cs
Normal file
599
breakpilot-drive/UnityScripts/BreakpilotAPI.cs
Normal file
@@ -0,0 +1,599 @@
|
||||
// ==============================================
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user