using System.Collections; using System.Collections.Generic; using TMPro; using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.UI; public class AnalogyLogicGameScene : MonoBehaviour { [Header("TXT (6 Language Analogy Data)")] public TextAsset analogyFile; [Header("UI")] public TMP_Text questionText; public TMP_Text[] answerTexts; public Button[] answerButtons; public Button submitButton; public TMP_Text feedbackText; public TMP_Text scoreText; public TMP_Text comboText; public TMP_Text timerText; public TMP_Text selectedWordsText; [Header("EXTRAS")] public Button extraTimeButton; public Button extraHintButton; public Button extraSkipButton; [Header("━━━ BACK BUTTON ━━━━━━━━━━━━━━━━━━━━━━━━")] public Button backButton; public string backSceneName = "ModSec 11"; [Header("COLORS")] public Color normalColor = new Color32(255, 255, 255, 70); public Color selectedColor = new Color32(60, 110, 200, 255); public Color correctColor = new Color32(0, 200, 0, 255); public Color wrongColor = new Color32(200, 0, 0, 255); [Header("━━━ FEEDBACK TEXT SETTINGS ━━━━━━━━━━━━━")] public bool feedbackOnTop = true; public float feedbackTopY = 400f; public float feedbackFontSize = 72f; [Header("━━━ ANIMATION SETTINGS ━━━━━━━━━━━━━━━━━")] [Range(0.80f, 1f)] public float selectedScale = 0.94f; public float selectAnimTime = 0.10f; public float enterAnimTime = 0.22f; // 0=TR, 1=EN, 2=ES, 3=DE, 4=FR, 5=PT private string langCode; // "tr","en"... private int langIndex; // 0–5 private class AnalogyQ { // data[language, field] -> 0:A 1:B 2:C 3:COR 4:W1 5:W2 6:W3 public string[,] data = new string[6, 7]; } private List questions = new List(); private int correctIndex = -1; private int selectedIndex = -1; private bool locked = false; private int lastQ = -1; private int score = 0; private int combo = 0; private int correctCount = 0; private int wrongCount = 0; private float timeLeft = 60f; private float nextTimer = 0f; private string modKey = "Mod11"; readonly Color timerNormalColor = Color.white; readonly Color timerWarnColor = new Color32(255, 80, 80, 255); Vector3[] originalButtonScales; Vector3[] originalButtonPositions; Coroutine[] buttonScaleCoroutines; Coroutine[] buttonEnterCoroutines; Coroutine feedbackCo; // ================================================================ void Start() { // Uses the same system as SentenceOrderGameScene langCode = PlayerPrefs.GetString("QuestionLangCode", "en"); langIndex = GetLangIndex(); if (analogyFile == null) { Debug.LogError("AnalogyLogicGameScene: analogyFile is not assigned!"); if (questionText != null) questionText.text = "TXT NOT FOUND!"; enabled = false; return; } LoadTXT6Lang(analogyFile.text); if (questions.Count == 0) { Debug.LogError("AnalogyLogicGameScene: TXT is empty or invalid!"); if (questionText != null) questionText.text = "EMPTY FILE!"; enabled = false; return; } if (answerButtons == null || answerButtons.Length < 4 || answerTexts == null || answerTexts.Length < 4) { Debug.LogError("AnalogyLogicGameScene: 4 answerButtons / answerTexts are required."); enabled = false; return; } CacheButtonData(); PrepareFeedbackText(); Shuffle(questions); if (submitButton != null) submitButton.onClick.AddListener(OnSubmit); if (extraTimeButton != null) extraTimeButton.onClick.AddListener(() => timeLeft += 5f); if (extraHintButton != null) extraHintButton.onClick.AddListener(OnHint); if (extraSkipButton != null) extraSkipButton.onClick.AddListener(NextQuestion); if (backButton != null) { backButton.onClick.RemoveAllListeners(); backButton.onClick.AddListener(() => SceneManager.LoadScene(backSceneName)); } if (scoreText != null) scoreText.text = "0"; if (comboText != null) comboText.text = "0x"; NextQuestion(); } // ================================================================ void CacheButtonData() { if (answerButtons == null) return; int len = answerButtons.Length; originalButtonScales = new Vector3[len]; originalButtonPositions = new Vector3[len]; buttonScaleCoroutines = new Coroutine[len]; buttonEnterCoroutines = new Coroutine[len]; for (int i = 0; i < len; i++) { if (answerButtons[i] == null) continue; originalButtonScales[i] = answerButtons[i].transform.localScale; originalButtonPositions[i] = answerButtons[i].transform.localPosition; answerButtons[i].transition = Selectable.Transition.None; } } void PrepareFeedbackText() { if (feedbackText == null) return; if (feedbackOnTop) { Vector3 pos = feedbackText.transform.localPosition; pos.y = feedbackTopY; feedbackText.transform.localPosition = pos; } feedbackText.alignment = TextAlignmentOptions.Center; feedbackText.fontSize = Mathf.Max(feedbackText.fontSize, feedbackFontSize); feedbackText.text = ""; Color c = feedbackText.color; c.a = 1f; feedbackText.color = c; } // ================================================================ // 6-LANGUAGE BLOCK TXT READER // Each question: 6 lines (TR, EN, ES, DE, FR, PT) // Line format: A,B,C,COR,W1,W2,W3 // ================================================================ void LoadTXT6Lang(string raw) { string[] lines = raw.Split('\n'); for (int i = 0; i < lines.Length;) { if (string.IsNullOrWhiteSpace(lines[i])) { i++; continue; } if (i + 5 >= lines.Length) break; AnalogyQ q = new AnalogyQ(); bool validBlock = true; for (int L = 0; L < 6; L++) { string line = lines[i + L].Trim('\r', '\n', ' '); if (string.IsNullOrEmpty(line)) { validBlock = false; break; } string[] p = line.Split(','); if (p.Length != 7) { Debug.LogWarning($"AnalogyLogicGameScene: line {i + L} does not contain 7 parts, block skipped."); validBlock = false; break; } for (int k = 0; k < 7; k++) q.data[L, k] = p[k].Trim(); } if (validBlock) questions.Add(q); i += 6; } } // ================================================================ void Update() { if (!locked) { timeLeft -= Time.deltaTime; if (timeLeft <= 0f) { timeLeft = 0f; GameOver(); } } UpdateTimerUI(); if (locked && nextTimer > 0f) { nextTimer -= Time.deltaTime; if (nextTimer <= 0f) NextQuestion(); } } // ================================================================ void UpdateTimerUI() { if (timerText == null) return; timerText.text = Mathf.Ceil(timeLeft).ToString(); if (timeLeft <= 10f) { timerText.color = FullAlpha(Color.Lerp(timerWarnColor, Color.white, Mathf.PingPong(Time.time * 3f, 1f))); timerText.transform.localScale = Vector3.one * (1f + Mathf.Sin(Time.time * 10f) * 0.05f); } else { timerText.color = FullAlpha(timerNormalColor); timerText.transform.localScale = Vector3.one; } } // ================================================================ void NextQuestion() { locked = false; selectedIndex = -1; nextTimer = 0f; if (selectedWordsText != null) selectedWordsText.text = ""; if (feedbackText != null) feedbackText.text = ""; for (int i = 0; i < answerButtons.Length; i++) { if (answerButtons[i] == null) continue; StopButtonCoroutines(i); answerButtons[i].interactable = true; answerButtons[i].transform.localScale = originalButtonScales[i]; answerButtons[i].transform.localPosition = originalButtonPositions[i]; if (answerButtons[i].image != null) answerButtons[i].image.color = FullAlpha(normalColor); } if (questions.Count == 0) { GameOver(); return; } int id = Random.Range(0, questions.Count); if (id == lastQ && questions.Count > 1) id = (id + 1) % questions.Count; lastQ = id; AnalogyQ q = questions[id]; string A = q.data[langIndex, 0]; string B = q.data[langIndex, 1]; string C = q.data[langIndex, 2]; string COR = q.data[langIndex, 3]; string W1 = q.data[langIndex, 4]; string W2 = q.data[langIndex, 5]; string W3 = q.data[langIndex, 6]; if (questionText != null) { questionText.text = $"{A} : {B} = {C} : ?"; StartCoroutine(FadeInText(questionText)); } List opts = new List { COR, W1, W2, W3 }; Shuffle(opts); correctIndex = opts.IndexOf(COR); for (int i = 0; i < 4; i++) { if (answerTexts[i] != null) answerTexts[i].text = opts[i]; int x = i; if (answerButtons[i] != null) { answerButtons[i].onClick.RemoveAllListeners(); answerButtons[i].onClick.AddListener(() => OnSelect(x)); buttonEnterCoroutines[i] = StartCoroutine(ButtonEnterAnim(answerButtons[i], i)); } } } // ================================================================ void OnSelect(int id) { if (locked) return; if (selectedIndex == id) return; if (selectedIndex >= 0 && selectedIndex < answerButtons.Length && answerButtons[selectedIndex] != null) { if (answerButtons[selectedIndex].image != null) answerButtons[selectedIndex].image.color = FullAlpha(normalColor); StartScaleAnim(selectedIndex, originalButtonScales[selectedIndex]); } selectedIndex = id; if (answerButtons[id] != null) { if (answerButtons[id].image != null) answerButtons[id].image.color = FullAlpha(selectedColor); StartScaleAnim(id, originalButtonScales[id] * selectedScale); } if (selectedWordsText != null && answerTexts[id] != null) selectedWordsText.text = answerTexts[id].text; } // ================================================================ void OnSubmit() { if (locked) return; if (selectedIndex == -1) return; locked = true; bool ok = (selectedIndex == correctIndex); foreach (var b in answerButtons) if (b != null) b.interactable = false; if (ok) { correctCount++; combo++; score += 50 + combo * 10; timeLeft += 2f; if (answerButtons[selectedIndex] != null && answerButtons[selectedIndex].image != null) answerButtons[selectedIndex].image.color = FullAlpha(correctColor); StartCoroutine(CorrectPulse(answerButtons[selectedIndex], selectedIndex)); SetFeedback(combo >= 3 ? $"{Local("correct")} {combo}x COMBO!" : Local("correct"), correctColor); if (comboText != null) StartCoroutine(ComboAnim(comboText)); } else { wrongCount++; combo = 0; score -= 20; if (score < 0) score = 0; if (answerButtons[selectedIndex] != null && answerButtons[selectedIndex].image != null) answerButtons[selectedIndex].image.color = FullAlpha(wrongColor); StartCoroutine(ShakeButton(answerButtons[selectedIndex], selectedIndex)); if (correctIndex >= 0 && correctIndex < answerButtons.Length && answerButtons[correctIndex] != null && answerButtons[correctIndex].image != null) { answerButtons[correctIndex].image.color = FullAlpha(correctColor); StartCoroutine(CorrectPulse(answerButtons[correctIndex], correctIndex)); } SetFeedback(Local("wrong"), wrongColor); } if (scoreText != null) { scoreText.text = score.ToString(); StartCoroutine(ScorePopAnim(scoreText)); } if (comboText != null) comboText.text = combo + "x"; nextTimer = 1f; } // ================================================================ void OnHint() { if (locked) return; if (lastQ < 0 || lastQ >= questions.Count) return; string hint = questions[lastQ].data[langIndex, 3]; // correct answer if (!string.IsNullOrEmpty(hint)) hint = hint.Length > 1 ? hint[0] + "..." : hint; SetFeedback("Hint: " + hint, selectedColor); } // ================================================================ void SetFeedback(string msg, Color c) { if (feedbackText == null) return; feedbackText.text = msg; feedbackText.color = FullAlpha(c); if (feedbackOnTop) { Vector3 pos = feedbackText.transform.localPosition; pos.y = feedbackTopY; feedbackText.transform.localPosition = pos; } if (feedbackCo != null) StopCoroutine(feedbackCo); feedbackCo = StartCoroutine(FeedbackAnim(feedbackText)); } // ================================================================ void GameOver() { // 🔥 Simple key that the Mod Select / Leaderboard screen can bind to directly PlayerPrefs.SetInt("Mod11Skor", score); PlayerPrefs.SetString("LastPlayedMod", modKey); PlayerPrefs.SetInt(modKey + "_LastScore", score); int best = PlayerPrefs.GetInt(modKey + "_MaxScore", 0); if (score > best) PlayerPrefs.SetInt(modKey + "_MaxScore", score); PlayerPrefs.SetInt(modKey + "_Correct", correctCount); PlayerPrefs.SetInt(modKey + "_Wrong", wrongCount); PlayerPrefs.Save(); SceneManager.LoadScene("Mod11Resume"); } // ================================================================ int GetLangIndex() { return langCode switch { "tr" => 1, "en" => 0, "es" => 2, "de" => 3, "fr" => 4, "pt" => 5, _ => 1 // default EN }; } // ================================================================ string Local(string key) { int L = GetLangIndex(); // Same as QuestionLangCode switch (key) { case "correct": return new[] { "Correct!", "Correct!", "Correct!", "Correct!", "Correct!", "Correct!" }[L]; case "wrong": return new[] { "Wrong!", "Wrong!", "Wrong!", "Wrong!", "Wrong!", "Wrong!" }[L]; } return key; } // ================================================================ void Shuffle(IList arr) { for (int i = 0; i < arr.Count; i++) { int r = Random.Range(i, arr.Count); (arr[i], arr[r]) = (arr[r], arr[i]); } } // ================== ANIMATION HELPERS ================== void StartScaleAnim(int index, Vector3 targetScale) { if (index < 0 || index >= answerButtons.Length) return; if (answerButtons[index] == null) return; if (buttonScaleCoroutines[index] != null) StopCoroutine(buttonScaleCoroutines[index]); buttonScaleCoroutines[index] = StartCoroutine(ScaleButton(answerButtons[index], targetScale)); } void StopButtonCoroutines(int index) { if (buttonScaleCoroutines != null && index < buttonScaleCoroutines.Length && buttonScaleCoroutines[index] != null) { StopCoroutine(buttonScaleCoroutines[index]); buttonScaleCoroutines[index] = null; } if (buttonEnterCoroutines != null && index < buttonEnterCoroutines.Length && buttonEnterCoroutines[index] != null) { StopCoroutine(buttonEnterCoroutines[index]); buttonEnterCoroutines[index] = null; } } IEnumerator ScaleButton(Button btn, Vector3 targetScale) { if (btn == null) yield break; Vector3 startScale = btn.transform.localScale; float t = 0f; while (t < 1f) { t += Time.deltaTime / Mathf.Max(0.01f, selectAnimTime); btn.transform.localScale = Vector3.Lerp(startScale, targetScale, EaseOutQuad(t)); yield return null; } btn.transform.localScale = targetScale; } IEnumerator ButtonEnterAnim(Button btn, int delayIndex) { if (btn == null) yield break; int idx = System.Array.IndexOf(answerButtons, btn); if (idx < 0) yield break; yield return new WaitForSeconds(delayIndex * 0.06f); Vector3 targetPos = originalButtonPositions[idx]; Vector3 startPos = targetPos + Vector3.up * 35f; Vector3 targetScale = originalButtonScales[idx]; Vector3 startScale = targetScale * 0.85f; btn.transform.localPosition = startPos; btn.transform.localScale = startScale; float t = 0f; while (t < 1f) { t += Time.deltaTime / Mathf.Max(0.01f, enterAnimTime); float ease = EaseOutBack(t); btn.transform.localPosition = Vector3.Lerp(startPos, targetPos, ease); btn.transform.localScale = Vector3.Lerp(startScale, targetScale, ease); yield return null; } btn.transform.localPosition = targetPos; btn.transform.localScale = targetScale; } IEnumerator CorrectPulse(Button btn, int idx) { if (btn == null) yield break; if (idx < 0 || idx >= originalButtonScales.Length) yield break; Vector3 normal = originalButtonScales[idx]; Vector3 big = normal * 1.12f; float t = 0f; while (t < 1f) { t += Time.deltaTime / 0.12f; btn.transform.localScale = Vector3.Lerp(normal, big, EaseOutQuad(t)); yield return null; } t = 0f; while (t < 1f) { t += Time.deltaTime / 0.12f; btn.transform.localScale = Vector3.Lerp(big, normal, EaseOutQuad(t)); yield return null; } btn.transform.localScale = normal; } IEnumerator ShakeButton(Button btn, int idx) { if (btn == null) yield break; if (idx < 0 || idx >= originalButtonPositions.Length) yield break; Vector3 origin = originalButtonPositions[idx]; float duration = 0.35f; float magnitude = 12f; float elapsed = 0f; while (elapsed < duration) { float power = 1f - elapsed / duration; float x = Random.Range(-1f, 1f) * magnitude * power; btn.transform.localPosition = origin + new Vector3(x, 0f, 0f); elapsed += Time.deltaTime; yield return null; } btn.transform.localPosition = origin; } IEnumerator FeedbackAnim(TMP_Text txt) { if (txt == null) yield break; Vector3 originalScale = Vector3.one; Vector3 bigScale = originalScale * 1.18f; Color c = FullAlpha(txt.color); c.a = 0f; txt.color = c; Vector3 originalPos = txt.transform.localPosition; Vector3 startPos = originalPos + Vector3.down * 18f; txt.transform.localPosition = startPos; float t = 0f; while (t < 1f) { t += Time.deltaTime / 0.16f; float ease = EaseOutBack(t); txt.transform.localScale = Vector3.Lerp(originalScale, bigScale, ease); txt.transform.localPosition = Vector3.Lerp(startPos, originalPos, ease); c.a = Mathf.Clamp01(t); txt.color = c; yield return null; } c.a = 1f; txt.color = c; txt.transform.localPosition = originalPos; t = 0f; while (t < 1f) { t += Time.deltaTime / 0.12f; txt.transform.localScale = Vector3.Lerp(bigScale, originalScale, EaseOutQuad(t)); yield return null; } txt.transform.localScale = originalScale; feedbackCo = null; } IEnumerator ComboAnim(TMP_Text txt) { if (txt == null) yield break; Vector3 original = Vector3.one; Vector3 big = original * 1.22f; Color baseColor = FullAlpha(txt.color); Color highlight = new Color32(255, 220, 50, 255); float t = 0f; while (t < 1f) { t += Time.deltaTime / 0.12f; txt.transform.localScale = Vector3.Lerp(original, big, EaseOutBack(t)); txt.color = FullAlpha(Color.Lerp(baseColor, highlight, t)); yield return null; } t = 0f; while (t < 1f) { t += Time.deltaTime / 0.14f; txt.transform.localScale = Vector3.Lerp(big, original, EaseOutQuad(t)); txt.color = FullAlpha(Color.Lerp(highlight, baseColor, t)); yield return null; } txt.transform.localScale = original; txt.color = baseColor; } IEnumerator ScorePopAnim(TMP_Text txt) { if (txt == null) yield break; Vector3 original = Vector3.one; Vector3 big = original * 1.18f; float t = 0f; while (t < 1f) { t += Time.deltaTime / 0.10f; txt.transform.localScale = Vector3.Lerp(original, big, EaseOutQuad(t)); yield return null; } t = 0f; while (t < 1f) { t += Time.deltaTime / 0.12f; txt.transform.localScale = Vector3.Lerp(big, original, EaseOutQuad(t)); yield return null; } txt.transform.localScale = original; } IEnumerator FadeInText(TMP_Text txt) { if (txt == null) yield break; Color c = FullAlpha(txt.color); c.a = 0f; txt.color = c; float t = 0f; while (t < 1f) { t += Time.deltaTime / 0.18f; c.a = EaseOutQuad(t); txt.color = c; yield return null; } c.a = 1f; txt.color = c; } Color FullAlpha(Color c) { c.a = 1f; return c; } float EaseOutBack(float t) { t = Mathf.Clamp01(t); float c1 = 1.70158f; float c3 = c1 + 1f; return 1f + c3 * Mathf.Pow(t - 1f, 3f) + c1 * Mathf.Pow(t - 1f, 2f); } float EaseOutQuad(float t) { t = Mathf.Clamp01(t); return 1f - (1f - t) * (1f - t); } }