Commit 5f4e8e15 authored by Mahmoud Aglan's avatar Mahmoud Aglan

Update : Question Analytics is Live

parent aff49eaa
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AndroidProjectSystem">
<option name="providerId" value="RiderAndroidProjectSystem" />
</component>
</project>
\ No newline at end of file
fileFormatVersion: 2
guid: 745e9c17df962b24a80a69d5da8e5d38
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: d308d9efe86ef6242a75802e1f37de49
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: e5036f96e3c15ea49b96f7ee989dd3c1
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: 9e9f7f46a1ba34c338eb95b193ae1327
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: b18b93d4b5d00384ba417df18aeac5a3
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: 92a80e6f6cd90464b8f87b98fc72999a
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
......@@ -72,6 +72,7 @@ namespace com.al_arcade.cs
// ─── BaseGameManager implementation ──────────────────────────────────
protected override string GameTypeKey => "cs";
protected override int TotalQuestionsCount => _questions?.Length ?? 0;
protected override IEnumerator FetchQuestions(Action<string> onError)
{
......@@ -136,6 +137,15 @@ namespace com.al_arcade.cs
protected override IEnumerator OnTimeUp()
{
if (_currentIndex < _questions.Length)
{
var q = _questions[_currentIndex];
int elapsed = GetQuestionElapsedMs();
var attempt = BuildAttempt(q.id, false, elapsed);
attempt.isTimeout = 1;
ReportAttempt(attempt);
}
_state = CsGameState.Complete;
yield return LoseSequence();
}
......@@ -268,6 +278,7 @@ namespace com.al_arcade.cs
yield return new WaitForSeconds(sentenceShowDelay);
SpawnWords(question);
StartQuestionTimer();
yield return new WaitForSeconds(1.3f);
_state = CsGameState.WaitingForWordClick;
......@@ -313,6 +324,13 @@ namespace com.al_arcade.cs
_deltaChangeInSize++;
AdjustTimer(CsPrefabBuilder.Instance.correctAnswerBonusTime);
int elapsed = GetQuestionElapsedMs();
var attempt = BuildAttempt(question.id, true, elapsed);
attempt.selectedAnswer = question.correct_answer;
attempt.correctAnswer = question.correct_answer;
attempt.hintUsed = _wrongClicks > 0 ? 1 : 0;
ReportAttempt(attempt);
int points = Mathf.Max(100 - _wrongClicks * 15, 25);
if (_streak >= 3) points += (_streak - 2) * 25;
_score += points;
......
......@@ -46,6 +46,7 @@ namespace com.al_arcade.mcq
private int _bestStreak;
private List<McqGateController> _activeGates = new();
private int _correctGateIndex = -1;
private int _selectedGateIndex = -1;
private Camera _mainCamera;
private bool _isTicking;
......@@ -72,6 +73,7 @@ namespace com.al_arcade.mcq
}
protected override string GameTypeKey => "mcq";
protected override int TotalQuestionsCount => _questions?.Length ?? 0;
protected override IEnumerator FetchQuestions(Action<string> onError)
{
......@@ -139,6 +141,15 @@ namespace com.al_arcade.mcq
protected override IEnumerator OnTimeUp()
{
if (_currentIndex < _questions.Length)
{
var q = _questions[_currentIndex];
int elapsed = GetQuestionElapsedMs();
var attempt = BuildAttempt(q.id, false, elapsed);
attempt.isTimeout = 1;
ReportAttempt(attempt);
}
_state = McqGameState.GameOver;
StopPlayerAndCompetitor();
StopAllCoroutines();
......@@ -265,6 +276,7 @@ namespace com.al_arcade.mcq
_state = McqGameState.WaitingForAnswer;
SpawnGates(question);
StartQuestionTimer();
bool answered = false;
bool wasCorrect = false;
......@@ -273,6 +285,7 @@ namespace com.al_arcade.mcq
{
if (_state == McqGameState.GameOver) return;
answered = true;
_selectedGateIndex = idx;
wasCorrect = idx == _correctGateIndex;
}
......@@ -332,6 +345,13 @@ namespace com.al_arcade.mcq
{
if (_state == McqGameState.GameOver) yield break;
var q = _questions[_currentIndex];
int elapsed = GetQuestionElapsedMs();
var attempt = BuildAttempt(q.id, correct, elapsed);
attempt.selectedAnswer = (_selectedGateIndex + 1).ToString();
attempt.correctAnswer = (_correctGateIndex + 1).ToString();
ReportAttempt(attempt);
if (correct)
{
_correctCount++;
......
......@@ -6,6 +6,8 @@ using UnityEngine.Events;
namespace com.al_arcade.shared
{
public enum DeviceCategory { mobile, tablet, desktop, unknown }
/// <summary>
/// Abstract base class for all game managers (TF, MCQ, CS, ...).
/// Handles: singleton wiring, score/streak/counts, timer, API loading
......@@ -34,6 +36,8 @@ namespace com.al_arcade.shared
protected int _totalAsked;
protected DateTime gameStartTime;
protected string _sessionId;
protected float _questionStartTime;
// ─── Shared UnityEvents ──────────────────────────────────────────────
[Header("Base Events")]
......@@ -153,6 +157,7 @@ namespace com.al_arcade.shared
OnHideLoading();
yield return OnBeforeBeginGameplay();
_sessionId = Guid.NewGuid().ToString("N");
gameStartTime = DateTime.Now;
_timeLeft = maxTimePerQuestion;
......@@ -239,6 +244,57 @@ namespace com.al_arcade.shared
public int WrongCount => _wrongCount;
public int CurrentQuestionIndex => _currentIndex;
// ─── Analytics ────────────────────────────────────────────────────────
protected void StartQuestionTimer()
{
_questionStartTime = Time.realtimeSinceStartup;
}
protected int GetQuestionElapsedMs()
{
return Mathf.RoundToInt((Time.realtimeSinceStartup - _questionStartTime) * 1000f);
}
protected void ReportAttempt(AttemptData attempt)
{
var api = SSApiManager.EnsureInstance();
StartCoroutine(api.ReportAttempt(attempt));
}
protected AttemptData BuildAttempt(int questionId, bool correct, int timeMs)
{
var attempt = new AttemptData(GameTypeKey, questionId, _sessionId, correct, timeMs);
attempt.playerStreak = _streak;
attempt.playerScoreAtTime = _score;
attempt.roundInGame = _currentIndex + 1;
attempt.totalRounds = TotalQuestionsCount;
var user = UserService.Instance?.CurrentUser;
if (user != null)
attempt.playerId = user.Id;
attempt.deviceType = GetDeviceType();
attempt.os = SystemInfo.operatingSystem;
attempt.clientVersion = Application.version;
attempt.screenWidth = Screen.width;
attempt.screenHeight = Screen.height;
return attempt;
}
protected virtual int TotalQuestionsCount => 0;
private static string GetDeviceType()
{
#if UNITY_IOS || UNITY_ANDROID
float diagonal = Mathf.Sqrt(Screen.width * Screen.width + Screen.height * Screen.height) / Screen.dpi;
return diagonal >= 7f ? "tablet" : "mobile";
#else
return "desktop";
#endif
}
// ─── Challenge mode ───────────────────────────────────────────────────
private ChallengeManager _challengeManager;
private bool _challengeChecked;
......
......@@ -49,6 +49,7 @@ namespace com.al_arcade.tf
// ─── BaseGameManager implementation ──────────────────────────────────
protected override string GameTypeKey => "tf";
protected override int TotalQuestionsCount => _questions?.Length ?? 0;
protected override IEnumerator FetchQuestions(Action<string> onError)
{
......@@ -164,6 +165,7 @@ namespace com.al_arcade.tf
_waitingForAnswer = true;
_pendingAnswer = -1;
_state = TfGameState.Playing;
StartQuestionTimer();
if (handController != null) handController.SetReady(true);
......@@ -183,6 +185,12 @@ namespace com.al_arcade.tf
bool playerSaidTrue = _pendingAnswer == 1;
bool isCorrect = playerSaidTrue == q.is_true;
int elapsed = GetQuestionElapsedMs();
var attempt = BuildAttempt(q.id, isCorrect, elapsed);
attempt.selectedAnswer = playerSaidTrue ? "true" : "false";
attempt.correctAnswer = q.is_true ? "true" : "false";
ReportAttempt(attempt);
if (isCorrect)
{
_correctCount++;
......@@ -256,6 +264,15 @@ namespace com.al_arcade.tf
// ─── End sequences ────────────────────────────────────────────────────
private IEnumerator HandleTimeUp()
{
if (_currentIndex < _questions.Length)
{
var q = _questions[_currentIndex];
int elapsed = GetQuestionElapsedMs();
var attempt = BuildAttempt(q.id, false, elapsed);
attempt.isTimeout = 1;
ReportAttempt(attempt);
}
_state = TfGameState.GameOver;
yield return LoseSequence();
}
......
<Solution>
<Project Path="Unity.Timeline.csproj" />
<Project Path="Unity.VisualEffectGraph.Editor.csproj" />
<Project Path="Unity.RenderPipelines.GPUDriven.Runtime.csproj" />
<Project Path="Unity.PlasticSCM.Editor.csproj" />
<Project Path="Assembly-CSharp.csproj" />
<Project Path="Unity.ShaderGraph.Editor.csproj" />
<Project Path="Unity.RenderPipelines.Core.Runtime.csproj" />
<Project Path="Unity.Mathematics.csproj" />
<Project Path="Unity.Timeline.Editor.csproj" />
<Project Path="Unity.Cinemachine.Editor.csproj" />
<Project Path="Unity.Multiplayer.Center.Editor.csproj" />
<Project Path="Unity.RenderPipelines.Core.Editor.csproj" />
<Project Path="UnityEngine.UI.csproj" />
<Project Path="Unity.RenderPipelines.Universal.2D.Runtime.csproj" />
<Project Path="Unity.Collections.csproj" />
<Project Path="Unity.VisualScripting.Core.Editor.csproj" />
<Project Path="Unity.RenderPipelines.Universal.Editor.csproj" />
<Project Path="Unity.PerformanceTesting.Editor.csproj" />
<Project Path="Unity.Splines.csproj" />
<Project Path="UnityEngine.TestRunner.csproj" />
<Project Path="Unity.Burst.csproj" />
<Project Path="Unity.VisualScripting.Flow.Editor.csproj" />
<Project Path="Unity.Recorder.Editor.csproj" />
<Project Path="Unity.VisualStudio.Editor.csproj" />
<Project Path="Unity.InputSystem.csproj" />
<Project Path="PPv2URPConverters.csproj" />
<Project Path="LightSide.UniText.csproj" />
<Project Path="UniTask.csproj" />
<Project Path="Unity.Searcher.Editor.csproj" />
<Project Path="UnityEditor.TestRunner.csproj" />
<Project Path="Unity.VisualScripting.Core.csproj" />
<Project Path="Unity.RenderPipelines.Universal.Runtime.csproj" />
<Project Path="Unity.UnifiedRayTracing.Runtime.csproj" />
<Project Path="Unity.AI.Navigation.Editor.ConversionSystem.csproj" />
<Project Path="Unity.VisualScripting.State.Editor.csproj" />
<Project Path="Unity.VisualEffectGraph.Runtime.csproj" />
<Project Path="Unity.Burst.CodeGen.csproj" />
<Project Path="Unity.Cinemachine.csproj" />
<Project Path="Unity.Postprocessing.Editor.csproj" />
<Project Path="FlatKit.Utils.Editor.csproj" />
<Project Path="Unity.VisualScripting.Flow.csproj" />
<Project Path="Unity.Splines.Editor.csproj" />
<Project Path="Unity.PerformanceTesting.csproj" />
<Project Path="Unity.TextMeshPro.csproj" />
<Project Path="UniTask.Linq.csproj" />
<Project Path="Unity.TextMeshPro.Editor.csproj" />
<Project Path="ExternAttributes.Editor.csproj" />
<Project Path="Unity.Burst.Editor.csproj" />
<Project Path="Nobi.UiRoundedCorners.Editor.csproj" />
<Project Path="Unity.Rider.Editor.csproj" />
<Project Path="Unity.VisualScripting.State.csproj" />
<Project Path="Unity.AI.Navigation.Updater.csproj" />
<Project Path="UnityEditor.UI.csproj" />
<Project Path="Unity.RenderPipelines.Universal.2D.Editor.Overrides.csproj" />
<Project Path="Unity.Postprocessing.Runtime.csproj" />
<Project Path="CFXRRuntime.csproj" />
<Project Path="LightSide.UniText.Editor.csproj" />
<Project Path="Unity.2D.Sprite.Editor.csproj" />
<Project Path="NuGetForUnity.csproj" />
<Project Path="Unity.AI.Navigation.Editor.csproj" />
<Project Path="CFXRDemo.csproj" />
<Project Path="Unity.Recorder.csproj" />
<Project Path="CFXREditor.csproj" />
<Project Path="Unity.Multiplayer.Center.Common.csproj" />
<Project Path="Assembly-CSharp-firstpass.csproj" />
<Project Path="Unity.InputSystem.ForUI.csproj" />
<Project Path="Unity.AI.Navigation.csproj" />
<Project Path="Unity.Collections.CodeGen.csproj" />
<Project Path="Assembly-CSharp-Editor.csproj" />
<Project Path="Unity.Mathematics.Editor.csproj" />
<Project Path="Unity.VisualScripting.SettingsProvider.Editor.csproj" />
<Project Path="UnityEditor.UI.Analytics.csproj" />
<Project Path="UniTask.TextMeshPro.csproj" />
<Project Path="Unity.Settings.Editor.csproj" />
<Project Path="Unity.InputSystem.TestFramework.csproj" />
<Project Path="Unity.ShaderGraph.Utilities.csproj" />
<Project Path="Unity.VisualScripting.Shared.Editor.csproj" />
<Project Path="Unity.Recorder.Base.csproj" />
<Project Path="KinoBloom.Runtime.csproj" />
<Project Path="Nobi.UiRoundedCorners.csproj" />
<Project Path="Unity.CollabProxy.Editor.csproj" />
<Project Path="CFXR.WelcomeScreen.csproj" />
<Project Path="Unity.RenderPipeline.Universal.ShaderLibrary.csproj" />
<Project Path="UniTask.Editor.csproj" />
<Project Path="ToonyColorsPro.Demo.Editor.csproj" />
<Project Path="Unity.Bindings.OpenImageIO.Editor.csproj" />
<Project Path="Unity.InputSystem.DocCodeSamples.csproj" />
<Project Path="Unity.RenderPipelines.Universal.Shaders.csproj" />
<Project Path="Unity.RenderPipelines.Core.Runtime.Shared.csproj" />
<Project Path="Unity.InternalAPIEngineBridge.004.csproj" />
<Project Path="Unity.RenderPipelines.Core.Editor.Shared.csproj" />
<Project Path="Unity.VisualScripting.DocCodeExamples.csproj" />
<Project Path="Unity.PlasticSCM.Editor.Entities.csproj" />
<Project Path="UniTask.Addressables.csproj" />
<Project Path="Unity.Rendering.LightTransport.Editor.csproj" />
<Project Path="Unity.Collections.Editor.csproj" />
<Project Path="UniTask.DOTween.csproj" />
<Project Path="Unity.RenderPipelines.Universal.Config.Runtime.csproj" />
<Project Path="Unity.RenderPipelines.ShaderGraph.ShaderGraphLibrary.csproj" />
<Project Path="Unity.RenderPipelines.Core.ShaderLibrary.csproj" />
</Solution>
......@@ -66,14 +66,14 @@
"url": "https://packages.unity.com"
},
"com.unity.collections": {
"version": "2.6.5",
"version": "2.6.2",
"depth": 1,
"source": "registry",
"dependencies": {
"com.unity.burst": "1.8.27",
"com.unity.burst": "1.8.23",
"com.unity.mathematics": "1.3.2",
"com.unity.test-framework": "1.4.6",
"com.unity.nuget.mono-cecil": "1.11.6",
"com.unity.nuget.mono-cecil": "1.11.5",
"com.unity.test-framework.performance": "3.0.3"
},
"url": "https://packages.unity.com"
......@@ -217,7 +217,7 @@
}
},
"com.unity.splines": {
"version": "2.8.4",
"version": "2.8.2",
"depth": 1,
"source": "registry",
"dependencies": {
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment