Commit 63c006e8 authored by Yousef Sameh's avatar Yousef Sameh

Merge remote-tracking branch 'origin/New-MCQ' into Supabase

parents 534d2cf9 0ed084a6
This source diff could not be displayed because it is too large. You can view the blob instead.
fileFormatVersion: 2
guid: dc77d4f7977b6514e96035771e5e547f
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: 20ba488acbcf34d438c13bfb24840820
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
using UnityEngine;
using UnityEngine.UI;
using TMPro;
using System;
using LightSide;
public class AnswerButtonUI : MonoBehaviour
{
[SerializeField] private Button button;
[SerializeField] private Image answerImageComponent;
[SerializeField] private UniText answerTextComponent;
[SerializeField] private LayoutGroup answerLayout;
[SerializeField] private Image buttonBackgroundImage;
[SerializeField] private Color normalColor = Color.white;
[SerializeField] private Color normalTextColor = Color.black;
[SerializeField] private Color selectedCorrectColor = Color.green;
[SerializeField] private Color selectedIncorrectColor = Color.red;
[SerializeField] private Color correctAnswerColor = new Color(0.2f, 0.8f, 0.2f, 1f); // Light green
[SerializeField] private Color disabledColor = new Color(0.7f, 0.7f, 0.7f, 1f); // Gray
private int answerIndex;
private Action<int> onClicked;
private bool isInteractable = true;
private void OnEnable()
{
if (button != null)
{
button.onClick.RemoveAllListeners();
}
}
/// <summary>
/// Setup answer button with text and optional image
/// </summary>
public void Setup(string answerText, Sprite answerImage, int index, Action<int> callback)
{
button.Select();
answerIndex = index;
onClicked = callback;
isInteractable = true;
// Setup text
if (answerTextComponent != null)
{
answerTextComponent.Text = answerText;
answerTextComponent.color = normalTextColor;
}
// Setup image
if (answerImageComponent != null)
{
if (answerImage != null)
{
answerImageComponent.sprite = answerImage;
answerImageComponent.gameObject.SetActive(true);
}
else
{
answerImageComponent.gameObject.SetActive(false);
}
}
// Setup button
if (button != null)
{
button.onClick.RemoveAllListeners();
button.onClick.AddListener(() => HandleClick());
button.interactable = true;
}
// Reset background color
if (buttonBackgroundImage != null)
{
buttonBackgroundImage.color = normalColor;
}
// Rebuild layout
if (answerLayout != null)
{
LayoutRebuilder.ForceRebuildLayoutImmediate(answerLayout.transform as RectTransform);
}
}
/// <summary>
/// Handle button click
/// </summary>
private void HandleClick()
{
if (!isInteractable || button == null || !button.interactable)
return;
onClicked?.Invoke(answerIndex);
}
/// <summary>
/// Mark this answer as selected and correct
/// </summary>
public void SetSelected(bool isCorrect)
{
isInteractable = false;
if (buttonBackgroundImage != null)
{
buttonBackgroundImage.color = isCorrect ? selectedCorrectColor : selectedIncorrectColor;
}
if (button != null)
{
button.interactable = false;
}
}
/// <summary>
/// Show this as the correct answer (when user selected wrong)
/// </summary>
public void SetCorrect()
{
isInteractable = false;
if (buttonBackgroundImage != null)
{
buttonBackgroundImage.color = correctAnswerColor;
}
if (button != null)
{
button.interactable = false;
}
}
/// <summary>
/// Disable this button without highlighting
/// </summary>
public void SetDisabled()
{
isInteractable = false;
if (buttonBackgroundImage != null)
{
buttonBackgroundImage.color = disabledColor;
}
if (button != null)
{
button.interactable = false;
}
}
/// <summary>
/// Reset button to normal state
/// </summary>
public void Reset()
{
isInteractable = true;
if (buttonBackgroundImage != null)
{
buttonBackgroundImage.color = normalColor;
}
if (answerTextComponent != null)
{
answerTextComponent.color = normalTextColor;
}
if (button != null)
{
button.interactable = true;
}
}
/// <summary>
/// Get answer index
/// </summary>
public int GetAnswerIndex()
{
return answerIndex;
}
/// <summary>
/// Check if button is interactable
/// </summary>
public bool IsInteractable()
{
return isInteractable;
}
}
\ No newline at end of file
fileFormatVersion: 2
guid: cb8a50d45c5491e45a8c75803f8df92c
\ No newline at end of file
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.Events;
using DG.Tweening;
using com.al_arcade.shared;
using com.al_arcade.mcq;
namespace com.al_arcade.mcq
{
public class NewMCQGameManger : MonoBehaviour
{
[SerializeField] private int totalLives = 5;
[SerializeField] private float questionTime = 5;
[SerializeField] private float streakBonusThreshold = 3;
[Header("Audio — SFX Clips")]
[SerializeField] private AudioClip sfxCorrect;
[SerializeField] private AudioClip sfxWrong;
[SerializeField] private AudioClip sfxClick;
[SerializeField] private AudioClip sfxVictory;
[SerializeField] private AudioClip sfxDefeat;
[SerializeField] private AudioClip sfxWhoosh;
[SerializeField] private AudioClip sfxPop;
[SerializeField] private AudioClip sfxCheer;
[SerializeField] private AudioClip sfxCountdown;
private McqQuestion[] _questions;
private McqQuestion currentQuestion;
private int currentQuestionIndex;
private int _score, _streak, _bestStreak, _lives;
private int _correctCount, _wrongCount;
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start()
{
SetupAudioManager();
_lives = totalLives;
if (NewMCQUIManager.Instance != null)
{
NewMCQUIManager.Instance.SetLives(totalLives, totalLives);
NewMCQUIManager.Instance.SetScore(0);
NewMCQUIManager.Instance.ShowGameUI();
}
StartCoroutine(StartGame());
}
private IEnumerator StartGame()
{
NewMCQUIManager.Instance.ShowLoading("جاري تحميل الأسئلة...");
var session = SSGameSession.EnsureInstance();
var api = SSApiManager.EnsureInstance();
string error = null;
yield return api.FetchMcq(
session.buildType, session.classCode,
session.questionCount, session.gradeId,
qs => _questions = qs,
err => error = err
);
if (error != null || _questions == null || _questions.Length == 0)
{
NewMCQUIManager.Instance.ShowError(error ?? "لا توجد أسئلة متاحة");
yield break;
}
NewMCQUIManager.Instance.HideLoading();
ShowNextQuestion();
}
private void ShowNextQuestion()
{
NewMCQUIManager.Instance.SetProgress(currentQuestionIndex, _questions.Length);
if (currentQuestionIndex > _questions.Length)
{
StartCoroutine(VictorySequence());
return;
}
McqQuestion question = _questions[currentQuestionIndex];
QuestionUi.Instance.DisplayQuestion(question, OnAnswerSubmitted);
}
private void OnAnswerSubmitted(int selectedIndex, bool isCorrect)
{
Debug.Log($"Answer Index: {selectedIndex}, Correct:{isCorrect}");
if (isCorrect)
{
_correctCount++;
_streak++;
if (_bestStreak < _streak)
{
_bestStreak = _streak;
}
int points = 100;
if (_streak >= streakBonusThreshold)
points += (_streak - (int)streakBonusThreshold + 1) * 25;
_score += points;
ShowCorrectFeedback(points);
var audio = SSAudioManager.Instance;
if (audio != null)
{
if (audio.sfxCorrect != null) audio.PlayCorrect();
else audio.PlayCorrectBeep();
}
}
else
{
_streak = 0;
_wrongCount++;
_lives--;
ShowWrongFeedback();
NewMCQUIManager.Instance.SetScore(_score);
NewMCQUIManager.Instance.SetLives(_lives, totalLives);
if (_lives <= 0)
{
StartCoroutine(GameOverSequence());
return;
}
var audio = SSAudioManager.Instance;
if (audio != null)
{
if (audio.sfxWrong != null) audio.PlayWrong();
else audio.PlayWrongBeep();
}
}
Invoke(nameof(GoToNextQuestion),1f);
}
private void GoToNextQuestion()
{
currentQuestionIndex++;
QuestionUi.Instance.ResetForNextQuestion();
ShowNextQuestion();
}
private IEnumerator VictorySequence()
{
var audio = SSAudioManager.Instance;
if (audio != null)
{ if (audio.sfxVictory != null) audio.PlayVictory(); else audio.PlaySuccessJingle(); }
var particles = SSParticleManager.Instance;
if (particles != null) particles.PlayScreenConfetti();
yield return new WaitForSeconds(0.5f);
NewMCQUIManager.Instance.ShowResults(_score, _correctCount, _wrongCount,
_bestStreak, _questions.Length, true);
}
private IEnumerator GameOverSequence()
{
var audio = SSAudioManager.Instance;
if (audio != null)
{ if (audio.sfxDefeat != null) audio.PlayDefeat(); else audio.PlayFailBuzz(); }
yield return new WaitForSeconds(0.5f);
NewMCQUIManager.Instance.ShowResults(_score, _correctCount, _wrongCount,
_bestStreak, _questions.Length, false);
}
private void ShowCorrectFeedback(int points)
{
if (NewMCQUIManager.Instance != null)
{
string msg = _streak >= streakBonusThreshold
? $"ممتاز! +{points} (سلسلة {_streak}×)"
: $"صحيح! +{points}";
NewMCQUIManager.Instance.ShowFeedback(msg, true);
}
}
private void ShowWrongFeedback()
{
if (NewMCQUIManager.Instance != null) NewMCQUIManager.Instance.ShowFeedback("خطأ!", false);
if (Camera.main != null)
{
DOTween.Kill(Camera.main.transform, "camShake");
Camera.main.transform.DOShakePosition(0.4f, 0.3f, 15, 90f, false, true)
.SetEase(Ease.OutQuad).SetId("camShake");
}
}
private void SetupAudioManager()
{
var a = SSAudioManager.EnsureInstance();
if (sfxCorrect != null) a.sfxCorrect = sfxCorrect;
if (sfxWrong != null) a.sfxWrong = sfxWrong;
if (sfxClick != null) a.sfxClick = sfxClick;
if (sfxVictory != null) a.sfxVictory = sfxVictory;
if (sfxDefeat != null) a.sfxDefeat = sfxDefeat;
if (sfxWhoosh != null) a.sfxWhoosh = sfxWhoosh;
if (sfxPop != null) a.sfxPop = sfxPop;
if (sfxCheer != null) a.sfxCheer = sfxCheer;
if (sfxCountdown != null) a.sfxCountdown = sfxCountdown;
}
}
}
fileFormatVersion: 2
guid: 8b4109c5dc3e965418f041d490b09d96
\ No newline at end of file
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Events;
// using ALArcade.ArabicTMP;
namespace com.al_arcade.mcq
{
using DG.Tweening;
using LightSide;
using shared;
using UnityEngine.SceneManagement;
public class NewMCQUIManager : MonoBehaviour
{
public static NewMCQUIManager Instance;
[Header("CanvasGroups")]
[SerializeField] private CanvasGroup _gameUI;
[SerializeField] private CanvasGroup _loadingUI;
[SerializeField] private CanvasGroup _errorUI;
[SerializeField] private CanvasGroup _resultsUI;
[SerializeField] private CanvasGroup _feedbackUI;
[Header("Game UI")]
[SerializeField] private UniText _scoreText;
[SerializeField] private UniText _streakText;
[SerializeField] private UniText _progressText;
[SerializeField] private UniText _loadingText;
[SerializeField] private UniText _errorText;
[SerializeField] private UniText _feedbackText;
[SerializeField] private Image _feedbackBg;
[SerializeField] private Image[] _heartIcons;
[Header("Results UI")]
[SerializeField] private UniText _resultTitle;
[SerializeField] private UniText _resultScore;
[SerializeField] private UniText _resultCorrect;
[SerializeField] private UniText _resultWrong;
[SerializeField] private UniText _resultStreak;
[SerializeField] private Button _resultRestartBtn;
[SerializeField] private Slider _progressSlider;
[SerializeField] private GameObject _winIcon;
[SerializeField] private GameObject _loseIcon;
private Image _progressFill;
[Header("Events")]
public UnityEvent onRestartClicked;
private void Awake()
{
if (_gameUI != null)
{
_gameUI.alpha = 0; _gameUI.gameObject.SetActive(false);
}
if (_loadingUI != null)
{
_loadingUI.alpha = 0; _loadingUI.gameObject.SetActive(false);
}
if (_errorUI != null)
{
_errorUI.alpha = 0; _errorUI.gameObject.SetActive(false);
}
if (_resultsUI != null)
{
_resultsUI.alpha = 0; _resultsUI.gameObject.SetActive(false);
}
if (_feedbackUI != null)
{
_feedbackUI.alpha = 0; _feedbackUI.gameObject.SetActive(true);
}
if (_winIcon != null) _winIcon.SetActive(false);
if (_loseIcon != null) _loseIcon.SetActive(false);
if(Instance != null)
{
Debug.LogError("there is two ui manager");
}
else
{
Instance = this;
}
}
private void Start()
{
_resultRestartBtn.onClick.AddListener(() => { SceneManager.LoadScene(SceneManager.GetActiveScene().name); });
}
public void ShowGameUI()
{
_gameUI.gameObject.SetActive(true);
_gameUI.DOFade(1f, 0.5f);
}
public void SetScore(int score)
{
if (_scoreText == null) return;
_scoreText.Text = score.ToString("N0");
DOTween.Kill(_scoreText.transform, "scorePunch");
_scoreText.transform.DOPunchScale(Vector3.one * 0.2f, 0.3f, 6, 0.3f)
.SetId("scorePunch");
}
public void SetStreak(int streak)
{
if (_streakText == null) return;
_streakText.Text = streak > 1 ? $"🔥 ×{streak}" : "";
if (streak > 1)
{
DOTween.Kill(_streakText.transform, "streakPop");
_streakText.transform.localScale = Vector3.one * 1.3f;
_streakText.transform.DOScale(Vector3.one, 0.4f)
.SetEase(Ease.OutElastic).SetId("streakPop");
}
}
public void SetLives(int lives, int maxLives)
{
for (int i = 0; i < _heartIcons.Length; i++)
{
bool active = i < maxLives;
_heartIcons[i].gameObject.SetActive(active);
if (active)
{
bool alive = i < lives;
_heartIcons[i].color = alive
? SSColorPalette.Danger
: SSColorPalette.WithAlpha(SSColorPalette.Danger, 0.2f);
if (!alive && i == lives)
{
DOTween.Kill(_heartIcons[i].transform);
_heartIcons[i].transform.DOPunchScale(Vector3.one * 0.5f, 0.3f);
}
}
}
}
public void SetProgress(int current, int total)
{
if (_progressText != null) _progressText.Text = $"{current} / {total}";
if (_progressFill != null && total > 0)
{
float t = (float)current / total;
DOTween.Kill(_progressFill.rectTransform, "progFill");
_progressFill.rectTransform.DOAnchorMax(new Vector2(t, 1f), 0.5f)
.SetEase(Ease.OutQuad).SetId("progFill");
}
if (_progressSlider != null)
{
DOTween.Kill("progSlider");
DOVirtual.Float(_progressSlider.value, (float)current, 0.5f, score =>
{
_progressSlider.value = score;
}).SetEase(Ease.OutQuad).SetId("progSlider");
}
}
public void ShowFeedback(string message, bool isCorrect)
{
if (_feedbackText != null) _feedbackText.Text = message;
if (_feedbackBg != null)
_feedbackBg.color = SSColorPalette.WithAlpha(
isCorrect ? SSColorPalette.Success : SSColorPalette.Danger, 0.92f);
DOTween.Kill(_feedbackUI);
_feedbackUI.alpha = 0;
var bgRect = _feedbackBg?.rectTransform;
if (bgRect != null)
{
print("Showing Feedback");
bgRect.localScale = new Vector3(0.5f, 0f, 1f);
var seq = DOTween.Sequence();
seq.Append(_feedbackUI.DOFade(1f, 0.15f));
seq.Join(bgRect.DOScaleX(1f, 0.25f).SetEase(Ease.OutBack));
seq.Join(bgRect.DOScaleY(1f, 0.2f).SetEase(Ease.OutBack).SetDelay(0.05f));
seq.AppendInterval(1.2f);
seq.Append(_feedbackUI.DOFade(0f, 0.3f));
}
}
public void ShowLoading(string msg)
{
_loadingUI.gameObject.SetActive(true);
if (_loadingText != null) _loadingText.Text = msg;
_loadingUI.DOFade(1f, 0.3f);
}
public void HideLoading()
{
_loadingUI.DOFade(0f, 0.3f)
.OnComplete(() => _loadingUI.gameObject.SetActive(false));
}
public void ShowError(string msg)
{
_errorUI.gameObject.SetActive(true);
if (_errorText != null) _errorText.Text = msg;
_errorUI.DOFade(1f, 0.3f);
}
public void ShowResults(int score, int correct, int wrong,
int bestStreak, int total, bool won)
{
_resultsUI.gameObject.SetActive(true);
_resultsUI.alpha = 0;
if (_resultTitle != null) _resultTitle.Text = won ? "أحسنت!" : "حظ أوفر!";
if (_resultScore != null) _resultScore.Text = score.ToString("N0");
if (_resultCorrect != null) _resultCorrect.Text = $"صحيح: {correct}";
if (_resultWrong != null) _resultWrong.Text = $"خطأ: {wrong}";
if (_resultStreak != null) _resultStreak.Text = $"أعلى سلسلة: {bestStreak}";
if (_winIcon != null) _winIcon.SetActive(won);
if (_loseIcon != null) _loseIcon.SetActive(!won);
var seq = DOTween.Sequence();
seq.Append(_resultsUI.DOFade(1f, 0.5f));
if (_resultTitle != null)
{
_resultTitle.transform.localScale = Vector3.zero;
seq.Append(_resultTitle.transform.DOScale(1f, 0.5f).SetEase(Ease.OutBack));
}
if (_winIcon != null)
{
_winIcon.transform.localScale = Vector3.zero;
seq.Append(_winIcon.transform.DOScale(1f, 0.5f).SetEase(Ease.OutBack));
}
if (_loseIcon != null)
{
_loseIcon.transform.localScale = Vector3.zero;
seq.Append(_loseIcon.transform.DOScale(1f, 0.5f).SetEase(Ease.OutBack));
}
if (_resultScore != null)
{
_resultScore.transform.localScale = Vector3.zero;
seq.Append(_resultScore.transform.DOScale(1f, 0.4f).SetEase(Ease.OutBack));
}
}
public void HideResults()
{
_resultsUI.DOFade(0f, 0.3f)
.OnComplete(() => _resultsUI.gameObject.SetActive(false));
}
public void RestartButtonFunction()
{
HideResults();
onRestartClicked?.Invoke();
McqGameManager.Instance.ResetGame();
McqGameManager.Instance.StartGame();
}
public void ResetUI()
{
_gameUI.gameObject.SetActive(false);
_loadingUI.gameObject.SetActive(false);
_errorUI.gameObject.SetActive(false);
_resultsUI.gameObject.SetActive(false);
_feedbackUI.alpha = 0;
}
}
}
fileFormatVersion: 2
guid: 168f088242b9231408ef684c9094c66e
\ No newline at end of file
using UnityEngine;
using UnityEngine.UI;
using TMPro;
using System;
using com.al_arcade.shared;
using LightSide;
public class QuestionUi : MonoBehaviour
{
public static QuestionUi Instance;
[SerializeField] private Image questionImageComponent;
[SerializeField] private UniText questionTextComponent;
[SerializeField] private Transform answersGrid;
[SerializeField] private AnswerButtonUI[] answerButtons = new AnswerButtonUI[4];
[SerializeField] private LayoutGroup questionContainer;
[SerializeField] private LayoutGroup answersContainer;
private McqQuestion currentQuestion;
private int correctAnswerIndex = -1;
private bool hasAnswered = false;
private Action<int, bool> onAnswerSelected;
private void Awake()
{
if (Instance == null)
{
Instance = this;
}
else
{
Debug.LogError("there is two questionUI");
}
}
private void OnEnable()
{
if (answerButtons.Length != 4)
{
Debug.LogError("MultiChoiceQuestionUI: Must have exactly 4 answer buttons!");
}
}
/// <summary>
/// Display a multiple choice question with all its data
/// </summary>
public void DisplayQuestion(McqQuestion question, Action<int, bool> onAnswerCallback = null)
{
if (question == null)
{
Debug.LogError("MultiChoiceQuestionUI: Question is null!");
return;
}
currentQuestion = question;
onAnswerSelected = onAnswerCallback;
hasAnswered = false;
correctAnswerIndex = -1;
// Reset previous shuffle
question.ResetShuffle();
// Setup question display
SetupQuestion(question);
// Get shuffled answers WITH images
question.GetShuffledAnswersWithImages(
out string[] shuffledAnswers,
out Sprite[] shuffledImages,
out int correctIndex
);
correctAnswerIndex = correctIndex;
// Setup answer buttons with shuffled data
SetupAnswerButtons(shuffledAnswers, shuffledImages);
//RectTransform example = answerButtons[1].GetComponent<RectTransform>();
//(answersContainer as GridLayoutGroup).cellSize = new Vector2(example.rect.width, example.rect.height);
// Rebuild layout hierarchy
LayoutRebuilder.ForceRebuildLayoutImmediate(questionContainer.GetComponent<RectTransform>());
LayoutRebuilder.ForceRebuildLayoutImmediate(answersContainer.GetComponent<RectTransform>());
}
/// <summary>
/// Setup the question text and image
/// </summary>
private void SetupQuestion(McqQuestion question)
{
// Setup question text
questionTextComponent.Text = question.question_text;
// Setup question image (if exists)
if (question.questionImage != null)
{
questionImageComponent.sprite = question.questionImage;
questionImageComponent.gameObject.SetActive(true);
}
else
{
questionImageComponent.gameObject.SetActive(false);
}
}
/// <summary>
/// Setup all 4 answer buttons with text and images
/// </summary>
private void SetupAnswerButtons(string[] answers, Sprite[] images)
{
if (answers.Length != 4 || images.Length != 4)
{
Debug.LogError("MultiChoiceQuestionUI: Answer arrays must have exactly 4 elements!");
return;
}
for (int i = 0; i < 4; i++)
{
answerButtons[i].Setup(
answerText: answers[i],
answerImage: images[i],
index: i,
callback: OnAnswerClicked
);
}
}
/// <summary>
/// Called when an answer button is clicked
/// </summary>
private void OnAnswerClicked(int selectedIndex)
{
Debug.Log("pressed");
if (hasAnswered)
return;
hasAnswered = true;
bool isCorrect = selectedIndex == correctAnswerIndex;
// Show visual feedback
ShowAnswerFeedback(selectedIndex, isCorrect);
// Invoke callback
onAnswerSelected?.Invoke(selectedIndex, isCorrect);
}
/// <summary>
/// Show visual feedback for selected answer
/// </summary>
private void ShowAnswerFeedback(int selectedIndex, bool isCorrect)
{
for (int i = 0; i < 4; i++)
{
if (i == selectedIndex)
{
answerButtons[i].SetSelected(isCorrect);
}
else if (i == correctAnswerIndex && !isCorrect)
{
answerButtons[i].SetCorrect();
}
else
{
answerButtons[i].SetDisabled();
}
}
}
/// <summary>
/// Reset all buttons for next question
/// </summary>
public void ResetForNextQuestion()
{
hasAnswered = false;
correctAnswerIndex = -1;
foreach (var button in answerButtons)
{
button.Reset();
}
}
/// <summary>
/// Get current question
/// </summary>
public McqQuestion GetCurrentQuestion()
{
return currentQuestion;
}
/// <summary>
/// Get the correct answer index for current question
/// </summary>
public int GetCorrectAnswerIndex()
{
return correctAnswerIndex;
}
/// <summary>
/// Check if user has answered
/// </summary>
public bool HasAnswered()
{
return hasAnswered;
}
}
\ No newline at end of file
fileFormatVersion: 2
guid: 68aee830395e108478a9643b20de1689
\ No newline at end of file
......@@ -61,28 +61,120 @@ namespace com.al_arcade.shared
{
public string id;
public string question_text;
public Sprite questionImage;
public string answer1;
public Sprite answer1Image;
public string answer2;
public Sprite answer2Image;
public string answer3;
public Sprite answer3Image;
public string answer4;
public Sprite answer4Image;
public string source;
public string grade_name;
// Private shuffle state (keeps track of current shuffle)
private int[] _shuffleIndices;
private int _cachedCorrectIndex = -1;
/// <summary>
/// Original method - unchanged for backwards compatibility
/// </summary>
public string[] GetShuffledAnswers(out int correctIndex)
{
string[] arr = { answer1, answer2, answer3, answer4 };
string correct = answer1;
for (int i = arr.Length - 1; i > 0; i--)
// Generate and cache shuffle indices
_shuffleIndices = new int[] { 0, 1, 2, 3 };
for (int i = _shuffleIndices.Length - 1; i > 0; i--)
{
int j = UnityEngine.Random.Range(0, i + 1);
(arr[i], arr[j]) = (arr[j], arr[i]);
(_shuffleIndices[i], _shuffleIndices[j]) = (_shuffleIndices[j], _shuffleIndices[i]);
}
// Apply shuffle
string[] shuffled = new string[4];
for (int i = 0; i < 4; i++)
{
shuffled[i] = arr[_shuffleIndices[i]];
}
correctIndex = Array.IndexOf(shuffled, correct);
_cachedCorrectIndex = correctIndex;
return shuffled;
}
/// <summary>
/// NEW: Get shuffled answers WITH images
/// Returns answer text + images in the same shuffled order
/// </summary>
public void GetShuffledAnswersWithImages(
out string[] answers,
out Sprite[] images,
out int correctIndex)
{
string[] textArr = { answer1, answer2, answer3, answer4 };
Sprite[] imageArr = { answer1Image, answer2Image, answer3Image, answer4Image };
string correct = answer1;
// Generate shuffle indices if not already done
if (_shuffleIndices == null || _shuffleIndices.Length == 0)
{
_shuffleIndices = new int[] { 0, 1, 2, 3 };
for (int i = _shuffleIndices.Length - 1; i > 0; i--)
{
int j = UnityEngine.Random.Range(0, i + 1);
(_shuffleIndices[i], _shuffleIndices[j]) = (_shuffleIndices[j], _shuffleIndices[i]);
}
}
// Apply shuffle to both text and images
string[] shuffledText = new string[4];
Sprite[] shuffledImages = new Sprite[4];
for (int i = 0; i < 4; i++)
{
int originalIndex = _shuffleIndices[i];
shuffledText[i] = textArr[originalIndex];
shuffledImages[i] = imageArr[originalIndex];
}
correctIndex = Array.IndexOf(arr, correct);
return arr;
answers = shuffledText;
images = shuffledImages;
correctIndex = Array.IndexOf(shuffledText, correct);
_cachedCorrectIndex = correctIndex;
}
/// <summary>
/// HELPER: Get image for a specific answer index (after shuffle)
/// Useful if you only have the index
/// </summary>
public Sprite GetAnswerImageByIndex(int shuffledIndex)
{
if (_shuffleIndices == null || _shuffleIndices.Length == 0)
return null;
int originalIndex = _shuffleIndices[shuffledIndex];
Sprite[] allImages = { answer1Image, answer2Image, answer3Image, answer4Image };
return allImages[originalIndex];
}
/// <summary>
/// HELPER: Get the cached correct index from last shuffle
/// </summary>
public int GetCachedCorrectIndex()
{
return _cachedCorrectIndex;
}
/// <summary>
/// HELPER: Reset shuffle state (call before new question)
/// </summary>
public void ResetShuffle()
{
_shuffleIndices = null;
_cachedCorrectIndex = -1;
}
}
......
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