starOfWords/Assets/Scripts/3.0/mod11/AnalogyGameScene.cs
portakal ecb1c9edea Initial commit: Star of Words Unity project
Snapshot of the existing Unity 6 language-learning word game before any
refactoring. Adds Unity .gitignore and .gitattributes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 15:31:20 +03:00

846 lines
No EOL
24 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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; // 05
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<AnalogyQ> questions = new List<AnalogyQ>();
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<string> opts = new List<string> { 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<T>(IList<T> 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);
}
}