From e24b30743be00c3095e80c9a4e19b9d7366c5106 Mon Sep 17 00:00:00 2001 From: Konstantin Dyachenko Date: Sat, 28 Feb 2026 19:25:26 +0700 Subject: [PATCH] [Fix] Code visual --- Assets/Scripts/Dice/Dice.cs | 231 ++++--- Assets/Scripts/Dice/DiceRoller.cs | 253 ++++--- Assets/Scripts/Economy/CurrencyBank.cs | 67 +- Assets/Scripts/Game/DebugGameInput.cs | 39 -- Assets/Scripts/Game/DebugGameInput.cs.meta | 2 - Assets/Scripts/Game/DiceManager.cs | 165 +++-- Assets/Scripts/Game/GameManager.cs | 171 +++-- .../Scripts/Inventory/InventoryController.cs | 141 ++-- Assets/Scripts/Inventory/InventoryModel.cs | 213 +++--- Assets/Scripts/Inventory/InventorySlotView.cs | 125 ++-- Assets/Scripts/Inventory/InventoryView.cs | 117 ++-- Assets/Scripts/Modifiers/ModifierData.cs | 167 +++-- Assets/Scripts/Modifiers/ModifierEffect.cs | 91 ++- Assets/Scripts/Modifiers/ModifierEnums.cs | 48 +- Assets/Scripts/Modifiers/ModifierPipeline.cs | 105 ++- Assets/Scripts/Modifiers/ModifierRuntime.cs | 55 +- Assets/Scripts/Modifiers/ModifierTarget.cs | 23 +- Assets/Scripts/Persistence/SaveData.cs | 29 +- Assets/Scripts/Persistence/SaveSystem.cs | 65 +- Assets/Scripts/Scoring/CategoryScorer.cs | 139 ++-- Assets/Scripts/Scoring/ScoreResult.cs | 41 +- Assets/Scripts/Scoring/ScoringSystem.cs | 151 +++-- Assets/Scripts/Scoring/YachtCategory.cs | 36 +- Assets/Scripts/Shop/ShopCatalog.cs | 27 +- Assets/Scripts/Shop/ShopController.cs | 83 ++- Assets/Scripts/Shop/ShopItemView.cs | 145 ++-- Assets/Scripts/Shop/ShopModel.cs | 141 ++-- Assets/Scripts/Shop/ShopView.cs | 117 ++-- .../Tests/Editor/InventoryModelTests.cs | 245 ++++--- .../Tests/Editor/ModifierEffectTests.cs | 127 ++-- .../Tests/Editor/ModifierPipelineTests.cs | 203 +++--- .../Scripts/Tests/Editor/SaveSystemTests.cs | 137 ++-- .../Tests/Editor/ScoringSystemTests.cs | 171 +++-- Assets/Scripts/Tests/Editor/ShopModelTests.cs | 201 +++--- Assets/Scripts/UI/CategoryRowView.cs | 147 ++--- Assets/Scripts/UI/DicePanelView.cs | 167 +++-- Assets/Scripts/UI/GameController.cs | 623 +++++++++--------- Assets/Scripts/UI/GameInfoView.cs | 111 ++-- Assets/Scripts/UI/ScoreCardView.cs | 179 +++-- 39 files changed, 2611 insertions(+), 2687 deletions(-) delete mode 100644 Assets/Scripts/Game/DebugGameInput.cs delete mode 100644 Assets/Scripts/Game/DebugGameInput.cs.meta diff --git a/Assets/Scripts/Dice/Dice.cs b/Assets/Scripts/Dice/Dice.cs index 9da3256..5eba589 100644 --- a/Assets/Scripts/Dice/Dice.cs +++ b/Assets/Scripts/Dice/Dice.cs @@ -4,138 +4,137 @@ using UnityEngine; namespace YachtDice.Dice { - -public sealed class Dice : MonoBehaviour -{ - [Serializable] - public struct Entry : IEquatable + public class Dice : MonoBehaviour { - [SerializeField] private int value; - [SerializeField] private Transform point; - - public int Value => value; - public Transform Point => point; - - public bool Equals(Entry other) => point == other.point; - public override bool Equals(object obj) => obj is Entry other && Equals(other); - public override int GetHashCode() => point != null ? point.GetHashCode() : 0; - } - - [SerializeField] private List entries = new(); - - private HashSet entrySet; - - private void Awake() => RebuildSet(); - private void OnValidate() => RebuildSet(); - - public void Test() - { - if (!TryGetTopValue(out var top) || !TryGetBottomValue(out var bottom)) - return; - - Debug.Log($"Top Value: {top}, Bottom Value: {bottom}"); - - AlignToTopByLocalAngles(); - } - - private void RebuildSet() - { - entrySet = new HashSet(); - if (entries == null) return; - for (int i = 0; i < entries.Count; i++) entrySet.Add(entries[i]); - } - - public bool TryGetTopValue(out int value) => TryGetExtremeValue(isTop: true, out value); - public bool TryGetBottomValue(out int value) => TryGetExtremeValue(isTop: false, out value); - - public int AlignToTopByLocalAngles() - { - var e = GetExtremeEntryByWorldY(isTop: true); - AlignByFaceLocalAngles(e.Point); - return e.Value; - } - - public int AlignToBottomByLocalAngles() - { - var e = GetExtremeEntryByWorldY(isTop: false); - AlignByFaceLocalAngles(e.Point); - return e.Value; - } - - private bool TryGetExtremeValue(bool isTop, out int value) - { - value = default; - - if (entrySet == null || entrySet.Count == 0) - return false; - - bool found = false; - float bestY = isTop ? float.NegativeInfinity : float.PositiveInfinity; - int best = default; - - foreach (var e in entrySet) + [Serializable] + public struct Entry : IEquatable { - var p = e.Point; - if (!p) continue; + [SerializeField] private int value; + [SerializeField] private Transform point; - float y = p.position.y; - if (!found || (isTop ? y > bestY : y < bestY)) - { - found = true; - bestY = y; - best = e.Value; - } + public int Value => value; + public Transform Point => point; + + public bool Equals(Entry other) => point == other.point; + public override bool Equals(object obj) => obj is Entry other && Equals(other); + public override int GetHashCode() => point != null ? point.GetHashCode() : 0; } - if (!found) return false; + [SerializeField] private List entries = new(); - value = best; - return true; - } + private HashSet entrySet; - private Entry GetExtremeEntryByWorldY(bool isTop) - { - if (entrySet == null || entrySet.Count == 0) - throw new InvalidOperationException("Dice: коллекция пуста."); + private void Awake() => RebuildSet(); + private void OnValidate() => RebuildSet(); - bool found = false; - float bestY = isTop ? float.NegativeInfinity : float.PositiveInfinity; - Entry best = default; - - foreach (var e in entrySet) + public void Test() { - var p = e.Point; - if (!p) continue; + if (!TryGetTopValue(out var top) || !TryGetBottomValue(out var bottom)) + return; - float y = p.position.y; - if (!found || (isTop ? y > bestY : y < bestY)) - { - found = true; - bestY = y; - best = e; - } + Debug.Log($"Top Value: {top}, Bottom Value: {bottom}"); + + AlignToTopByLocalAngles(); } - if (!found) - throw new InvalidOperationException("Dice: все Transform == null."); + private void RebuildSet() + { + entrySet = new HashSet(); + if (entries == null) return; + for (int i = 0; i < entries.Count; i++) entrySet.Add(entries[i]); + } - return best; - } + public bool TryGetTopValue(out int value) => TryGetExtremeValue(isTop: true, out value); + public bool TryGetBottomValue(out int value) => TryGetExtremeValue(isTop: false, out value); - private static float Norm180(float a) - { - a %= 360f; - return a > 180f ? a - 360f : (a < -180f ? a + 360f : a); - } + public int AlignToTopByLocalAngles() + { + var e = GetExtremeEntryByWorldY(isTop: true); + AlignByFaceLocalAngles(e.Point); + return e.Value; + } - private void AlignByFaceLocalAngles(Transform facePoint) - { - if (!facePoint) throw new InvalidOperationException("Dice: facePoint == null."); + public int AlignToBottomByLocalAngles() + { + var e = GetExtremeEntryByWorldY(isTop: false); + AlignByFaceLocalAngles(e.Point); + return e.Value; + } - transform.localEulerAngles = Vector3.Scale(facePoint.localEulerAngles, new Vector3(-1f, -1f, -1f)); + private bool TryGetExtremeValue(bool isTop, out int value) + { + value = default; - var e = transform.localEulerAngles; - transform.localEulerAngles = new Vector3(Norm180(e.x), Norm180(e.y), Norm180(e.z)); + if (entrySet == null || entrySet.Count == 0) + return false; + + bool found = false; + float bestY = isTop ? float.NegativeInfinity : float.PositiveInfinity; + int best = default; + + foreach (var e in entrySet) + { + var p = e.Point; + if (!p) continue; + + float y = p.position.y; + if (!found || (isTop ? y > bestY : y < bestY)) + { + found = true; + bestY = y; + best = e.Value; + } + } + + if (!found) return false; + + value = best; + return true; + } + + private Entry GetExtremeEntryByWorldY(bool isTop) + { + if (entrySet == null || entrySet.Count == 0) + throw new InvalidOperationException("Dice: коллекция пуста."); + + bool found = false; + float bestY = isTop ? float.NegativeInfinity : float.PositiveInfinity; + Entry best = default; + + foreach (var e in entrySet) + { + var p = e.Point; + if (!p) continue; + + float y = p.position.y; + if (!found || (isTop ? y > bestY : y < bestY)) + { + found = true; + bestY = y; + best = e; + } + } + + if (!found) + throw new InvalidOperationException("Dice: все Transform == null."); + + return best; + } + + private static float Norm180(float a) + { + a %= 360f; + return a > 180f ? a - 360f : (a < -180f ? a + 360f : a); + } + + private void AlignByFaceLocalAngles(Transform facePoint) + { + if (!facePoint) throw new InvalidOperationException("Dice: facePoint == null."); + + transform.localEulerAngles = Vector3.Scale(facePoint.localEulerAngles, new Vector3(-1f, -1f, -1f)); + + var e = transform.localEulerAngles; + transform.localEulerAngles = new Vector3(Norm180(e.x), Norm180(e.y), Norm180(e.z)); + } } } -} diff --git a/Assets/Scripts/Dice/DiceRoller.cs b/Assets/Scripts/Dice/DiceRoller.cs index 704727f..7e82deb 100644 --- a/Assets/Scripts/Dice/DiceRoller.cs +++ b/Assets/Scripts/Dice/DiceRoller.cs @@ -5,152 +5,151 @@ using Random = UnityEngine.Random; namespace YachtDice.Dice { - -/// -/// Красиво подбрасывает и раскручивает кубик, ждёт пока он остановится, -/// плавно доворачивает до ровного положения и сообщает результат. -/// -public sealed class DiceRoller : MonoBehaviour -{ - [Header("References")] - [SerializeField] private Dice dice; - [SerializeField] private Rigidbody rb; - - [Header("Throw Settings")] - [Tooltip("Сила подброса вверх")] - [SerializeField] private float throwUpForce = 6f; - - [Tooltip("Горизонтальный разброс")] - [SerializeField] private float throwScatter = 1.5f; - - [Tooltip("Минимальный крутящий момент")] - [SerializeField] private float torqueMin = 15f; - - [Tooltip("Максимальный крутящий момент")] - [SerializeField] private float torqueMax = 30f; - - [Header("Settle Detection")] - [Tooltip("Порог скорости, ниже которого кубик считается остановившимся")] - [SerializeField] private float settleSpeed = 0.05f; - - [Tooltip("Сколько секунд кубик должен быть неподвижен, чтобы засчитать остановку")] - [SerializeField] private float settleDelay = 0.3f; - - [Header("Snap Alignment")] - [Tooltip("Длительность плавного доворота к ровному положению")] - [SerializeField] private float snapDuration = 0.35f; - - [SerializeField] - private AnimationCurve snapCurve = AnimationCurve.EaseInOut(0f, 0f, 1f, 1f); - /// - /// Вызывается когда кубик полностью остановился. Аргумент — выпавшее значение. + /// Красиво подбрасывает и раскручивает кубик, ждёт пока он остановится, + /// плавно доворачивает до ровного положения и сообщает результат. /// - public event Action OnRollFinished; - - /// - /// Идёт ли сейчас бросок. - /// - public bool IsRolling { get; private set; } - - private Coroutine rollRoutine; - - private void Reset() + public class DiceRoller : MonoBehaviour { - dice = GetComponent(); - rb = GetComponent(); - } + [Header("References")] + [SerializeField] private Dice dice; + [SerializeField] private Rigidbody rb; + [Header("Throw Settings")] + [Tooltip("Сила подброса вверх")] + [SerializeField] private float throwUpForce = 6f; - /// - /// Бросить кубик. Повторный вызов во время броска игнорируется. - /// - public void Roll() - { - if (IsRolling) return; + [Tooltip("Горизонтальный разброс")] + [SerializeField] private float throwScatter = 1.5f; - if (rollRoutine != null) - StopCoroutine(rollRoutine); + [Tooltip("Минимальный крутящий момент")] + [SerializeField] private float torqueMin = 15f; - rollRoutine = StartCoroutine(RollSequence()); - } - - private IEnumerator RollSequence() - { - IsRolling = true; + [Tooltip("Максимальный крутящий момент")] + [SerializeField] private float torqueMax = 30f; - // ── 1. Подготовка ──────────────────────────────────────────── - rb.isKinematic = false; - rb.useGravity = true; - rb.linearVelocity = Vector3.zero; - rb.angularVelocity = Vector3.zero; + [Header("Settle Detection")] + [Tooltip("Порог скорости, ниже которого кубик считается остановившимся")] + [SerializeField] private float settleSpeed = 0.05f; - // ── 2. Импульс: подбросить вверх с лёгким разбросом ───────── - Vector3 force = new Vector3( - Random.Range(-throwScatter, throwScatter), - throwUpForce, - Random.Range(-throwScatter, throwScatter) - ); - rb.AddForce(force, ForceMode.Impulse); + [Tooltip("Сколько секунд кубик должен быть неподвижен, чтобы засчитать остановку")] + [SerializeField] private float settleDelay = 0.3f; - // ── 3. Случайный крутящий момент — красивое вращение ──────── - Vector3 torque = Random.onUnitSphere * Random.Range(torqueMin, torqueMax); - rb.AddTorque(torque, ForceMode.Impulse); + [Header("Snap Alignment")] + [Tooltip("Длительность плавного доворота к ровному положению")] + [SerializeField] private float snapDuration = 0.35f; - // Даём кубику взлететь - yield return new WaitForSeconds(0.2f); + [SerializeField] + private AnimationCurve snapCurve = AnimationCurve.EaseInOut(0f, 0f, 1f, 1f); + + /// + /// Вызывается когда кубик полностью остановился. Аргумент — выпавшее значение. + /// + public event Action OnRollFinished; + + /// + /// Идёт ли сейчас бросок. + /// + public bool IsRolling { get; private set; } - // ── 4. Ждём пока кубик успокоится ─────────────────────────── - float stillTimer = 0f; - float sqrThreshold = settleSpeed * settleSpeed; + private Coroutine rollRoutine; - while (stillTimer < settleDelay) + private void Reset() { - yield return new WaitForFixedUpdate(); - - bool isSlow = rb.linearVelocity.sqrMagnitude < sqrThreshold - && rb.angularVelocity.sqrMagnitude < sqrThreshold; - - stillTimer = isSlow ? stillTimer + Time.fixedDeltaTime : 0f; + dice = GetComponent(); + rb = GetComponent(); } - // ── 5. Замораживаем физику и читаем верхнюю грань ─────────── - rb.linearVelocity = Vector3.zero; - rb.angularVelocity = Vector3.zero; - rb.isKinematic = true; - if (!dice.TryGetTopValue(out int topValue)) + /// + /// Бросить кубик. Повторный вызов во время броска игнорируется. + /// + public void Roll() { - Debug.LogWarning("DiceRoller: не удалось определить верхнюю грань."); + if (IsRolling) return; + + if (rollRoutine != null) + StopCoroutine(rollRoutine); + + rollRoutine = StartCoroutine(RollSequence()); + } + + private IEnumerator RollSequence() + { + IsRolling = true; + + // ── 1. Подготовка ──────────────────────────────────────────── + rb.isKinematic = false; + rb.useGravity = true; + rb.linearVelocity = Vector3.zero; + rb.angularVelocity = Vector3.zero; + + // ── 2. Импульс: подбросить вверх с лёгким разбросом ───────── + Vector3 force = new Vector3( + Random.Range(-throwScatter, throwScatter), + throwUpForce, + Random.Range(-throwScatter, throwScatter) + ); + rb.AddForce(force, ForceMode.Impulse); + + // ── 3. Случайный крутящий момент — красивое вращение ──────── + Vector3 torque = Random.onUnitSphere * Random.Range(torqueMin, torqueMax); + rb.AddTorque(torque, ForceMode.Impulse); + + // Даём кубику взлететь + yield return new WaitForSeconds(0.2f); + + // ── 4. Ждём пока кубик успокоится ─────────────────────────── + float stillTimer = 0f; + float sqrThreshold = settleSpeed * settleSpeed; + + while (stillTimer < settleDelay) + { + yield return new WaitForFixedUpdate(); + + bool isSlow = rb.linearVelocity.sqrMagnitude < sqrThreshold + && rb.angularVelocity.sqrMagnitude < sqrThreshold; + + stillTimer = isSlow ? stillTimer + Time.fixedDeltaTime : 0f; + } + + // ── 5. Замораживаем физику и читаем верхнюю грань ─────────── + rb.linearVelocity = Vector3.zero; + rb.angularVelocity = Vector3.zero; + rb.isKinematic = true; + + if (!dice.TryGetTopValue(out int topValue)) + { + Debug.LogWarning("DiceRoller: не удалось определить верхнюю грань."); + IsRolling = false; + yield break; + } + + // ── 6. Плавный доворот до ровного положения ───────────────── + Quaternion startRot = transform.rotation; + + // Вычисляем целевой поворот через Dice.AlignToTopByLocalAngles + dice.AlignToTopByLocalAngles(); + Quaternion targetRot = transform.rotation; + + // Откатываемся обратно — будем интерполировать + transform.rotation = startRot; + + float elapsed = 0f; + while (elapsed < snapDuration) + { + elapsed += Time.deltaTime; + float t = snapCurve.Evaluate(Mathf.Clamp01(elapsed / snapDuration)); + transform.rotation = Quaternion.Slerp(startRot, targetRot, t); + yield return null; + } + transform.rotation = targetRot; + + // ── 7. Готово ─────────────────────────────────────────────── IsRolling = false; - yield break; + OnRollFinished?.Invoke(topValue); + + // Debug.Log($"{gameObject.name} | Выпало: {topValue}"); } - - // ── 6. Плавный доворот до ровного положения ───────────────── - Quaternion startRot = transform.rotation; - - // Вычисляем целевой поворот через Dice.AlignToTopByLocalAngles - dice.AlignToTopByLocalAngles(); - Quaternion targetRot = transform.rotation; - - // Откатываемся обратно — будем интерполировать - transform.rotation = startRot; - - float elapsed = 0f; - while (elapsed < snapDuration) - { - elapsed += Time.deltaTime; - float t = snapCurve.Evaluate(Mathf.Clamp01(elapsed / snapDuration)); - transform.rotation = Quaternion.Slerp(startRot, targetRot, t); - yield return null; - } - transform.rotation = targetRot; - - // ── 7. Готово ─────────────────────────────────────────────── - IsRolling = false; - OnRollFinished?.Invoke(topValue); - - // Debug.Log($"{gameObject.name} | Выпало: {topValue}"); } } -} diff --git a/Assets/Scripts/Economy/CurrencyBank.cs b/Assets/Scripts/Economy/CurrencyBank.cs index a6b9cbd..164d35b 100644 --- a/Assets/Scripts/Economy/CurrencyBank.cs +++ b/Assets/Scripts/Economy/CurrencyBank.cs @@ -3,46 +3,45 @@ using UnityEngine; namespace YachtDice.Economy { - -public sealed class CurrencyBank : MonoBehaviour -{ - [SerializeField] private int startingBalance = 500; - - private int balance; - - public int Balance => balance; - - public event Action OnBalanceChanged; - - private void Awake() + public class CurrencyBank : MonoBehaviour { - balance = startingBalance; - } + [SerializeField] private int startingBalance = 500; - public void Add(int amount) - { - if (amount <= 0) return; + private int balance; - balance += amount; - OnBalanceChanged?.Invoke(balance); - } + public int Balance => balance; - public bool Spend(int amount) - { - if (amount <= 0) return false; - if (balance < amount) return false; + public event Action OnBalanceChanged; - balance -= amount; - OnBalanceChanged?.Invoke(balance); - return true; - } + private void Awake() + { + balance = startingBalance; + } - public bool CanAfford(int amount) => balance >= amount; + public void Add(int amount) + { + if (amount <= 0) return; - public void SetBalance(int value) - { - balance = Mathf.Max(0, value); - OnBalanceChanged?.Invoke(balance); + balance += amount; + OnBalanceChanged?.Invoke(balance); + } + + public bool Spend(int amount) + { + if (amount <= 0) return false; + if (balance < amount) return false; + + balance -= amount; + OnBalanceChanged?.Invoke(balance); + return true; + } + + public bool CanAfford(int amount) => balance >= amount; + + public void SetBalance(int value) + { + balance = Mathf.Max(0, value); + OnBalanceChanged?.Invoke(balance); + } } } -} diff --git a/Assets/Scripts/Game/DebugGameInput.cs b/Assets/Scripts/Game/DebugGameInput.cs deleted file mode 100644 index 06c9ce1..0000000 --- a/Assets/Scripts/Game/DebugGameInput.cs +++ /dev/null @@ -1,39 +0,0 @@ -using UnityEngine; -using YachtDice.Scoring; - -namespace YachtDice.Game -{ - -public sealed class DebugGameInput : MonoBehaviour -{ - [SerializeField] private GameManager gameManager; - - private void Update() - { - if (Input.GetKeyDown(KeyCode.Space)) - gameManager.Roll(); - - // 1-5: toggle lock on dice 0-4 - if (Input.GetKeyDown(KeyCode.Alpha1)) gameManager.ToggleDiceLock(0); - if (Input.GetKeyDown(KeyCode.Alpha2)) gameManager.ToggleDiceLock(1); - if (Input.GetKeyDown(KeyCode.Alpha3)) gameManager.ToggleDiceLock(2); - if (Input.GetKeyDown(KeyCode.Alpha4)) gameManager.ToggleDiceLock(3); - if (Input.GetKeyDown(KeyCode.Alpha5)) gameManager.ToggleDiceLock(4); - - // Score categories - if (Input.GetKeyDown(KeyCode.F1)) gameManager.ScoreInCategory(YachtCategory.Ones); - if (Input.GetKeyDown(KeyCode.F2)) gameManager.ScoreInCategory(YachtCategory.Twos); - if (Input.GetKeyDown(KeyCode.F3)) gameManager.ScoreInCategory(YachtCategory.Threes); - if (Input.GetKeyDown(KeyCode.F4)) gameManager.ScoreInCategory(YachtCategory.Fours); - if (Input.GetKeyDown(KeyCode.F5)) gameManager.ScoreInCategory(YachtCategory.Fives); - if (Input.GetKeyDown(KeyCode.F6)) gameManager.ScoreInCategory(YachtCategory.Sixes); - if (Input.GetKeyDown(KeyCode.F7)) gameManager.ScoreInCategory(YachtCategory.ThreeOfAKind); - if (Input.GetKeyDown(KeyCode.F8)) gameManager.ScoreInCategory(YachtCategory.FourOfAKind); - if (Input.GetKeyDown(KeyCode.F9)) gameManager.ScoreInCategory(YachtCategory.FullHouse); - if (Input.GetKeyDown(KeyCode.F10)) gameManager.ScoreInCategory(YachtCategory.SmallStraight); - if (Input.GetKeyDown(KeyCode.F11)) gameManager.ScoreInCategory(YachtCategory.LargeStraight); - if (Input.GetKeyDown(KeyCode.F12)) gameManager.ScoreInCategory(YachtCategory.Yacht); - if (Input.GetKeyDown(KeyCode.C)) gameManager.ScoreInCategory(YachtCategory.Chance); - } -} -} diff --git a/Assets/Scripts/Game/DebugGameInput.cs.meta b/Assets/Scripts/Game/DebugGameInput.cs.meta deleted file mode 100644 index 68ed1c9..0000000 --- a/Assets/Scripts/Game/DebugGameInput.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 982f0996b85edfe419b07ddb36d6e235 \ No newline at end of file diff --git a/Assets/Scripts/Game/DiceManager.cs b/Assets/Scripts/Game/DiceManager.cs index 996e07e..f7eec67 100644 --- a/Assets/Scripts/Game/DiceManager.cs +++ b/Assets/Scripts/Game/DiceManager.cs @@ -5,104 +5,103 @@ using YachtDice.Dice; namespace YachtDice.Game { - -public sealed class DiceManager : MonoBehaviour -{ - [SerializeField] private List diceRollers = new(); - - public event Action OnAllDiceSettled; - public event Action OnDieSettled; - - public int DiceCount => diceRollers.Count; - - private bool[] locked; - private int[] currentValues; - private int pendingCount; - - private void Awake() + public class DiceManager : MonoBehaviour { - int count = diceRollers.Count; - locked = new bool[count]; - currentValues = new int[count]; - } + [SerializeField] private List diceRollers = new(); - public bool IsLocked(int index) => locked[index]; + public event Action OnAllDiceSettled; + public event Action OnDieSettled; - public void ToggleLock(int index) - { - locked[index] = !locked[index]; - } + public int DiceCount => diceRollers.Count; - public void SetLocked(int index, bool isLocked) - { - locked[index] = isLocked; - } + private bool[] locked; + private int[] currentValues; + private int pendingCount; - public void UnlockAll() - { - for (int i = 0; i < locked.Length; i++) locked[i] = false; - } - - public void RollUnlocked() - { - for (int i = 0; i < diceRollers.Count; i++) - if (diceRollers[i].IsRolling) return; - - pendingCount = 0; - - for (int i = 0; i < diceRollers.Count; i++) + private void Awake() { - if (locked[i]) continue; - - pendingCount++; - int capturedIndex = i; - - void Handler(int value) - { - diceRollers[capturedIndex].OnRollFinished -= Handler; - currentValues[capturedIndex] = value; - OnDieSettled?.Invoke(capturedIndex, value); - - pendingCount--; - if (pendingCount <= 0) - OnAllDiceSettled?.Invoke(); - } - - diceRollers[i].OnRollFinished += Handler; - diceRollers[i].Roll(); + int count = diceRollers.Count; + locked = new bool[count]; + currentValues = new int[count]; } - if (pendingCount == 0) - OnAllDiceSettled?.Invoke(); - } + public bool IsLocked(int index) => locked[index]; - public int[] GetCurrentValues() - { - int[] copy = new int[currentValues.Length]; - Array.Copy(currentValues, copy, currentValues.Length); - return copy; - } + public void ToggleLock(int index) + { + locked[index] = !locked[index]; + } - public int GetValue(int index) => currentValues[index]; + public void SetLocked(int index, bool isLocked) + { + locked[index] = isLocked; + } - public bool IsAnyRolling - { - get + public void UnlockAll() + { + for (int i = 0; i < locked.Length; i++) locked[i] = false; + } + + public void RollUnlocked() { for (int i = 0; i < diceRollers.Count; i++) - if (diceRollers[i].IsRolling) return true; - return false; - } - } + if (diceRollers[i].IsRolling) return; - public void ReadAllValues() - { - for (int i = 0; i < diceRollers.Count; i++) + pendingCount = 0; + + for (int i = 0; i < diceRollers.Count; i++) + { + if (locked[i]) continue; + + pendingCount++; + int capturedIndex = i; + + void Handler(int value) + { + diceRollers[capturedIndex].OnRollFinished -= Handler; + currentValues[capturedIndex] = value; + OnDieSettled?.Invoke(capturedIndex, value); + + pendingCount--; + if (pendingCount <= 0) + OnAllDiceSettled?.Invoke(); + } + + diceRollers[i].OnRollFinished += Handler; + diceRollers[i].Roll(); + } + + if (pendingCount == 0) + OnAllDiceSettled?.Invoke(); + } + + public int[] GetCurrentValues() { - var diceComponent = diceRollers[i].GetComponent(); - if (diceComponent != null && diceComponent.TryGetTopValue(out int val)) - currentValues[i] = val; + int[] copy = new int[currentValues.Length]; + Array.Copy(currentValues, copy, currentValues.Length); + return copy; + } + + public int GetValue(int index) => currentValues[index]; + + public bool IsAnyRolling + { + get + { + for (int i = 0; i < diceRollers.Count; i++) + if (diceRollers[i].IsRolling) return true; + return false; + } + } + + public void ReadAllValues() + { + for (int i = 0; i < diceRollers.Count; i++) + { + var diceComponent = diceRollers[i].GetComponent(); + if (diceComponent != null && diceComponent.TryGetTopValue(out int val)) + currentValues[i] = val; + } } } } -} diff --git a/Assets/Scripts/Game/GameManager.cs b/Assets/Scripts/Game/GameManager.cs index 76bfdcd..8c82cc9 100644 --- a/Assets/Scripts/Game/GameManager.cs +++ b/Assets/Scripts/Game/GameManager.cs @@ -4,102 +4,101 @@ using YachtDice.Scoring; namespace YachtDice.Game { - -public sealed class GameManager : MonoBehaviour -{ - [Header("References")] - [SerializeField] private DiceManager diceManager; - [SerializeField] private ScoringSystem scoringSystem; - - [Header("Settings")] - [SerializeField] private int maxRollsPerTurn = 3; - - public int CurrentRoll { get; private set; } - public int CurrentTurn { get; private set; } - - public bool CanRoll => CurrentRoll < maxRollsPerTurn && !diceManager.IsAnyRolling; - public bool CanScore => CurrentRoll > 0 && !diceManager.IsAnyRolling; - public bool IsGameOver => scoringSystem.IsComplete; - - public event Action OnTurnStarted; - public event Action OnRollComplete; - public event Action OnScored; - public event Action OnGameOver; - - private void Start() + public class GameManager : MonoBehaviour { - StartNewGame(); - } + [Header("References")] + [SerializeField] private DiceManager diceManager; + [SerializeField] private ScoringSystem scoringSystem; - public void StartNewGame() - { - scoringSystem.ResetScorecard(); - CurrentTurn = 0; - StartNewTurn(); - } + [Header("Settings")] + [SerializeField] private int maxRollsPerTurn = 3; - private void StartNewTurn() - { - CurrentTurn++; - CurrentRoll = 0; - diceManager.UnlockAll(); - OnTurnStarted?.Invoke(CurrentTurn); - Debug.Log($"=== Turn {CurrentTurn} ==="); - } + public int CurrentRoll { get; private set; } + public int CurrentTurn { get; private set; } - public void Roll() - { - if (!CanRoll) return; + public bool CanRoll => CurrentRoll < maxRollsPerTurn && !diceManager.IsAnyRolling; + public bool CanScore => CurrentRoll > 0 && !diceManager.IsAnyRolling; + public bool IsGameOver => scoringSystem.IsComplete; - CurrentRoll++; - diceManager.OnAllDiceSettled += HandleAllDiceSettled; - diceManager.RollUnlocked(); - } + public event Action OnTurnStarted; + public event Action OnRollComplete; + public event Action OnScored; + public event Action OnGameOver; - private void HandleAllDiceSettled() - { - diceManager.OnAllDiceSettled -= HandleAllDiceSettled; - - int[] values = diceManager.GetCurrentValues(); - Debug.Log($"Roll {CurrentRoll}/{maxRollsPerTurn} | Dice: [{string.Join(", ", values)}]"); - - OnRollComplete?.Invoke(CurrentRoll); - } - - public void ToggleDiceLock(int index) - { - if (diceManager.IsAnyRolling) return; - if (CurrentRoll == 0) return; - diceManager.ToggleLock(index); - - bool isLocked = diceManager.IsLocked(index); - Debug.Log($"Dice {index + 1} (value={diceManager.GetValue(index)}): {(isLocked ? "LOCKED" : "UNLOCKED")}"); - } - - public void ScoreInCategory(YachtCategory category) - { - if (!CanScore) return; - if (scoringSystem.IsCategoryUsed(category)) return; - - int[] values = diceManager.GetCurrentValues(); - ScoreResult result = scoringSystem.ScoreCategory(values, category); - - Debug.Log($"Scored {category}: base={result.BaseScore}, " + - $"bonus=+{result.FlatBonus}, mult=x{result.Multiplier:F1}, " + - $"FINAL={result.FinalScore} | Total={scoringSystem.TotalScore}"); - - OnScored?.Invoke(category, result.FinalScore); - - if (scoringSystem.IsComplete) + private void Start() { - int total = scoringSystem.TotalScore; - Debug.Log($"*** GAME OVER *** Total Score: {total}"); - OnGameOver?.Invoke(total); + StartNewGame(); } - else + + public void StartNewGame() { + scoringSystem.ResetScorecard(); + CurrentTurn = 0; StartNewTurn(); } + + private void StartNewTurn() + { + CurrentTurn++; + CurrentRoll = 0; + diceManager.UnlockAll(); + OnTurnStarted?.Invoke(CurrentTurn); + Debug.Log($"=== Turn {CurrentTurn} ==="); + } + + public void Roll() + { + if (!CanRoll) return; + + CurrentRoll++; + diceManager.OnAllDiceSettled += HandleAllDiceSettled; + diceManager.RollUnlocked(); + } + + private void HandleAllDiceSettled() + { + diceManager.OnAllDiceSettled -= HandleAllDiceSettled; + + int[] values = diceManager.GetCurrentValues(); + Debug.Log($"Roll {CurrentRoll}/{maxRollsPerTurn} | Dice: [{string.Join(", ", values)}]"); + + OnRollComplete?.Invoke(CurrentRoll); + } + + public void ToggleDiceLock(int index) + { + if (diceManager.IsAnyRolling) return; + if (CurrentRoll == 0) return; + diceManager.ToggleLock(index); + + bool isLocked = diceManager.IsLocked(index); + Debug.Log($"Dice {index + 1} (value={diceManager.GetValue(index)}): {(isLocked ? "LOCKED" : "UNLOCKED")}"); + } + + public void ScoreInCategory(YachtCategory category) + { + if (!CanScore) return; + if (scoringSystem.IsCategoryUsed(category)) return; + + int[] values = diceManager.GetCurrentValues(); + ScoreResult result = scoringSystem.ScoreCategory(values, category); + + Debug.Log($"Scored {category}: base={result.BaseScore}, " + + $"bonus=+{result.FlatBonus}, mult=x{result.Multiplier:F1}, " + + $"FINAL={result.FinalScore} | Total={scoringSystem.TotalScore}"); + + OnScored?.Invoke(category, result.FinalScore); + + if (scoringSystem.IsComplete) + { + int total = scoringSystem.TotalScore; + Debug.Log($"*** GAME OVER *** Total Score: {total}"); + OnGameOver?.Invoke(total); + } + else + { + StartNewTurn(); + } + } } } -} diff --git a/Assets/Scripts/Inventory/InventoryController.cs b/Assets/Scripts/Inventory/InventoryController.cs index f596635..45e80e0 100644 --- a/Assets/Scripts/Inventory/InventoryController.cs +++ b/Assets/Scripts/Inventory/InventoryController.cs @@ -5,94 +5,93 @@ using YachtDice.Scoring; namespace YachtDice.Inventory { - -public sealed class InventoryController : MonoBehaviour -{ - [SerializeField] private InventoryView inventoryView; - [SerializeField] private ScoringSystem scoringSystem; - [SerializeField] private CurrencyBank currencyBank; - - private InventoryModel model; - - public InventoryModel Model => model; - - public void Initialize(InventoryModel inventoryModel) + public class InventoryController : MonoBehaviour { - model = inventoryModel; + [SerializeField] private InventoryView inventoryView; + [SerializeField] private ScoringSystem scoringSystem; + [SerializeField] private CurrencyBank currencyBank; - inventoryView.OnActivateClicked += HandleActivate; - inventoryView.OnDeactivateClicked += HandleDeactivate; - inventoryView.OnSellClicked += HandleSell; + private InventoryModel model; - model.OnActiveModifiersChanged += HandleActiveModifiersChanged; - model.OnInventoryChanged += HandleInventoryChanged; + public InventoryModel Model => model; - if (scoringSystem != null) - scoringSystem.OnCategoryConfirmed += HandleCategoryConfirmed; - - RefreshView(); - } - - private void OnDestroy() - { - if (inventoryView != null) + public void Initialize(InventoryModel inventoryModel) { - inventoryView.OnActivateClicked -= HandleActivate; - inventoryView.OnDeactivateClicked -= HandleDeactivate; - inventoryView.OnSellClicked -= HandleSell; + model = inventoryModel; + + inventoryView.OnActivateClicked += HandleActivate; + inventoryView.OnDeactivateClicked += HandleDeactivate; + inventoryView.OnSellClicked += HandleSell; + + model.OnActiveModifiersChanged += HandleActiveModifiersChanged; + model.OnInventoryChanged += HandleInventoryChanged; + + if (scoringSystem != null) + scoringSystem.OnCategoryConfirmed += HandleCategoryConfirmed; + + RefreshView(); } - if (model != null) + private void OnDestroy() { - model.OnActiveModifiersChanged -= HandleActiveModifiersChanged; - model.OnInventoryChanged -= HandleInventoryChanged; + if (inventoryView != null) + { + inventoryView.OnActivateClicked -= HandleActivate; + inventoryView.OnDeactivateClicked -= HandleDeactivate; + inventoryView.OnSellClicked -= HandleSell; + } + + if (model != null) + { + model.OnActiveModifiersChanged -= HandleActiveModifiersChanged; + model.OnInventoryChanged -= HandleInventoryChanged; + } + + if (scoringSystem != null) + scoringSystem.OnCategoryConfirmed -= HandleCategoryConfirmed; } - if (scoringSystem != null) - scoringSystem.OnCategoryConfirmed -= HandleCategoryConfirmed; - } + private void HandleActivate(ModifierRuntime runtime) + { + model.TryActivate(runtime); + } - private void HandleActivate(ModifierRuntime runtime) - { - model.TryActivate(runtime); - } + private void HandleDeactivate(ModifierRuntime runtime) + { + model.Deactivate(runtime); + } - private void HandleDeactivate(ModifierRuntime runtime) - { - model.Deactivate(runtime); - } + private void HandleSell(ModifierRuntime runtime) + { + if (runtime.Data == null) return; - private void HandleSell(ModifierRuntime runtime) - { - if (runtime.Data == null) return; + int sellPrice = runtime.Data.SellPrice; + model.RemoveModifier(runtime); - int sellPrice = runtime.Data.SellPrice; - model.RemoveModifier(runtime); + if (currencyBank != null) + currencyBank.Add(sellPrice); + } - if (currencyBank != null) - currencyBank.Add(sellPrice); - } + private void HandleActiveModifiersChanged(System.Collections.Generic.List activeModifiers) + { + if (scoringSystem != null) + scoringSystem.SetActiveModifiers(activeModifiers); + } - private void HandleActiveModifiersChanged(System.Collections.Generic.List activeModifiers) - { - if (scoringSystem != null) - scoringSystem.SetActiveModifiers(activeModifiers); - } + private void HandleInventoryChanged() + { + RefreshView(); + } - private void HandleInventoryChanged() - { - RefreshView(); - } + private void HandleCategoryConfirmed(YachtCategory category, ScoreResult result) + { + model.ConsumeUseOnActive(); + } - private void HandleCategoryConfirmed(YachtCategory category, ScoreResult result) - { - model.ConsumeUseOnActive(); - } - - private void RefreshView() - { - if (inventoryView != null && model != null) - inventoryView.Refresh(model.OwnedModifiers, model.MaxActiveSlots); + private void RefreshView() + { + if (inventoryView != null && model != null) + inventoryView.Refresh(model.OwnedModifiers, model.MaxActiveSlots); + } } } -} diff --git a/Assets/Scripts/Inventory/InventoryModel.cs b/Assets/Scripts/Inventory/InventoryModel.cs index ab11621..397de7d 100644 --- a/Assets/Scripts/Inventory/InventoryModel.cs +++ b/Assets/Scripts/Inventory/InventoryModel.cs @@ -4,129 +4,128 @@ using YachtDice.Modifiers; namespace YachtDice.Inventory { - -public sealed class InventoryModel -{ - private readonly List ownedModifiers = new(); - private int maxActiveSlots; - - public IReadOnlyList OwnedModifiers => ownedModifiers; - public int MaxActiveSlots => maxActiveSlots; - - public event Action OnInventoryChanged; - public event Action> OnActiveModifiersChanged; - - public InventoryModel(int maxActiveSlots = 5) + public class InventoryModel { - this.maxActiveSlots = maxActiveSlots; - } + private readonly List ownedModifiers = new(); + private int maxActiveSlots; - public int ActiveCount - { - get + public IReadOnlyList OwnedModifiers => ownedModifiers; + public int MaxActiveSlots => maxActiveSlots; + + public event Action OnInventoryChanged; + public event Action> OnActiveModifiersChanged; + + public InventoryModel(int maxActiveSlots = 5) { - int count = 0; - for (int i = 0; i < ownedModifiers.Count; i++) - if (ownedModifiers[i].IsActive) count++; - return count; - } - } - - public void SetMaxActiveSlots(int slots) - { - maxActiveSlots = slots; - } - - public void AddModifier(ModifierData data) - { - var runtime = ModifierRuntime.Create(data); - ownedModifiers.Add(runtime); - OnInventoryChanged?.Invoke(); - } - - public void RemoveModifier(ModifierRuntime modifier) - { - if (!ownedModifiers.Contains(modifier)) return; - - if (modifier.IsActive) - { - modifier.IsActive = false; - OnActiveModifiersChanged?.Invoke(GetActiveModifierData()); + this.maxActiveSlots = maxActiveSlots; } - ownedModifiers.Remove(modifier); - OnInventoryChanged?.Invoke(); - } - - public bool TryActivate(ModifierRuntime modifier) - { - if (modifier.IsActive) return false; - if (!ownedModifiers.Contains(modifier)) return false; - if (ActiveCount >= maxActiveSlots) return false; - - modifier.IsActive = true; - OnActiveModifiersChanged?.Invoke(GetActiveModifierData()); - OnInventoryChanged?.Invoke(); - return true; - } - - public void Deactivate(ModifierRuntime modifier) - { - if (!modifier.IsActive) return; - - modifier.IsActive = false; - OnActiveModifiersChanged?.Invoke(GetActiveModifierData()); - OnInventoryChanged?.Invoke(); - } - - public void ConsumeUseOnActive() - { - bool changed = false; - - for (int i = ownedModifiers.Count - 1; i >= 0; i--) + public int ActiveCount { - var mod = ownedModifiers[i]; - if (!mod.IsActive) continue; - if (mod.Data == null) continue; - if (mod.Data.Durability != ModifierDurability.LimitedUses) continue; - - mod.ConsumeUse(); - - if (mod.IsExpired) + get { - ownedModifiers.RemoveAt(i); - changed = true; + int count = 0; + for (int i = 0; i < ownedModifiers.Count; i++) + if (ownedModifiers[i].IsActive) count++; + return count; } } - if (changed) + public void SetMaxActiveSlots(int slots) { + maxActiveSlots = slots; + } + + public void AddModifier(ModifierData data) + { + var runtime = ModifierRuntime.Create(data); + ownedModifiers.Add(runtime); + OnInventoryChanged?.Invoke(); + } + + public void RemoveModifier(ModifierRuntime modifier) + { + if (!ownedModifiers.Contains(modifier)) return; + + if (modifier.IsActive) + { + modifier.IsActive = false; + OnActiveModifiersChanged?.Invoke(GetActiveModifierData()); + } + + ownedModifiers.Remove(modifier); + OnInventoryChanged?.Invoke(); + } + + public bool TryActivate(ModifierRuntime modifier) + { + if (modifier.IsActive) return false; + if (!ownedModifiers.Contains(modifier)) return false; + if (ActiveCount >= maxActiveSlots) return false; + + modifier.IsActive = true; + OnActiveModifiersChanged?.Invoke(GetActiveModifierData()); + OnInventoryChanged?.Invoke(); + return true; + } + + public void Deactivate(ModifierRuntime modifier) + { + if (!modifier.IsActive) return; + + modifier.IsActive = false; OnActiveModifiersChanged?.Invoke(GetActiveModifierData()); OnInventoryChanged?.Invoke(); } - } - public List GetActiveModifierData() - { - var result = new List(); - for (int i = 0; i < ownedModifiers.Count; i++) + public void ConsumeUseOnActive() { - if (ownedModifiers[i].IsActive && ownedModifiers[i].Data != null) - result.Add(ownedModifiers[i].Data); + bool changed = false; + + for (int i = ownedModifiers.Count - 1; i >= 0; i--) + { + var mod = ownedModifiers[i]; + if (!mod.IsActive) continue; + if (mod.Data == null) continue; + if (mod.Data.Durability != ModifierDurability.LimitedUses) continue; + + mod.ConsumeUse(); + + if (mod.IsExpired) + { + ownedModifiers.RemoveAt(i); + changed = true; + } + } + + if (changed) + { + OnActiveModifiersChanged?.Invoke(GetActiveModifierData()); + OnInventoryChanged?.Invoke(); + } } - return result; + + public List GetActiveModifierData() + { + var result = new List(); + for (int i = 0; i < ownedModifiers.Count; i++) + { + if (ownedModifiers[i].IsActive && ownedModifiers[i].Data != null) + result.Add(ownedModifiers[i].Data); + } + return result; + } + + public void LoadState(List loaded) + { + ownedModifiers.Clear(); + if (loaded != null) + ownedModifiers.AddRange(loaded); + + OnActiveModifiersChanged?.Invoke(GetActiveModifierData()); + OnInventoryChanged?.Invoke(); + } + + public List GetAllForSave() => new(ownedModifiers); } - - public void LoadState(List loaded) - { - ownedModifiers.Clear(); - if (loaded != null) - ownedModifiers.AddRange(loaded); - - OnActiveModifiersChanged?.Invoke(GetActiveModifierData()); - OnInventoryChanged?.Invoke(); - } - - public List GetAllForSave() => new(ownedModifiers); -} } diff --git a/Assets/Scripts/Inventory/InventorySlotView.cs b/Assets/Scripts/Inventory/InventorySlotView.cs index c48b180..5b378b3 100644 --- a/Assets/Scripts/Inventory/InventorySlotView.cs +++ b/Assets/Scripts/Inventory/InventorySlotView.cs @@ -6,78 +6,77 @@ using YachtDice.Modifiers; namespace YachtDice.Inventory { - -public sealed class InventorySlotView : MonoBehaviour -{ - [SerializeField] private Image iconImage; - [SerializeField] private TMP_Text nameText; - [SerializeField] private TMP_Text descriptionText; - [SerializeField] private TMP_Text usesText; - [SerializeField] private Button activateButton; - [SerializeField] private Button deactivateButton; - [SerializeField] private Button sellButton; - [SerializeField] private TMP_Text sellPriceText; - [SerializeField] private Image background; - - [Header("Colors")] - [SerializeField] private Color activeColor = new(0.7f, 1f, 0.7f); - [SerializeField] private Color inactiveColor = Color.white; - - private ModifierRuntime runtime; - - public event Action OnActivateClicked; - public event Action OnDeactivateClicked; - public event Action OnSellClicked; - - private void Awake() + public class InventorySlotView : MonoBehaviour { - if (activateButton != null) - activateButton.onClick.AddListener(() => OnActivateClicked?.Invoke(runtime)); - if (deactivateButton != null) - deactivateButton.onClick.AddListener(() => OnDeactivateClicked?.Invoke(runtime)); - if (sellButton != null) - sellButton.onClick.AddListener(() => OnSellClicked?.Invoke(runtime)); - } + [SerializeField] private Image iconImage; + [SerializeField] private TMP_Text nameText; + [SerializeField] private TMP_Text descriptionText; + [SerializeField] private TMP_Text usesText; + [SerializeField] private Button activateButton; + [SerializeField] private Button deactivateButton; + [SerializeField] private Button sellButton; + [SerializeField] private TMP_Text sellPriceText; + [SerializeField] private Image background; - public void Setup(ModifierRuntime modifierRuntime, bool canActivateMore) - { - runtime = modifierRuntime; - var data = runtime.Data; + [Header("Colors")] + [SerializeField] private Color activeColor = new(0.7f, 1f, 0.7f); + [SerializeField] private Color inactiveColor = Color.white; - if (data == null) return; + private ModifierRuntime runtime; - if (nameText != null) nameText.text = data.DisplayName; - if (descriptionText != null) descriptionText.text = data.Description; - if (iconImage != null && data.Icon != null) iconImage.sprite = data.Icon; + public event Action OnActivateClicked; + public event Action OnDeactivateClicked; + public event Action OnSellClicked; - if (usesText != null) + private void Awake() { - if (data.Durability == ModifierDurability.LimitedUses) - { - usesText.gameObject.SetActive(true); - usesText.text = $"{runtime.RemainingUses}/{data.MaxUses}"; - } - else - { - usesText.gameObject.SetActive(false); - } + if (activateButton != null) + activateButton.onClick.AddListener(() => OnActivateClicked?.Invoke(runtime)); + if (deactivateButton != null) + deactivateButton.onClick.AddListener(() => OnDeactivateClicked?.Invoke(runtime)); + if (sellButton != null) + sellButton.onClick.AddListener(() => OnSellClicked?.Invoke(runtime)); } - if (sellPriceText != null) sellPriceText.text = data.SellPrice.ToString(); - - bool isActive = runtime.IsActive; - - if (activateButton != null) + public void Setup(ModifierRuntime modifierRuntime, bool canActivateMore) { - activateButton.gameObject.SetActive(!isActive); - activateButton.interactable = canActivateMore; + runtime = modifierRuntime; + var data = runtime.Data; + + if (data == null) return; + + if (nameText != null) nameText.text = data.DisplayName; + if (descriptionText != null) descriptionText.text = data.Description; + if (iconImage != null && data.Icon != null) iconImage.sprite = data.Icon; + + if (usesText != null) + { + if (data.Durability == ModifierDurability.LimitedUses) + { + usesText.gameObject.SetActive(true); + usesText.text = $"{runtime.RemainingUses}/{data.MaxUses}"; + } + else + { + usesText.gameObject.SetActive(false); + } + } + + if (sellPriceText != null) sellPriceText.text = data.SellPrice.ToString(); + + bool isActive = runtime.IsActive; + + if (activateButton != null) + { + activateButton.gameObject.SetActive(!isActive); + activateButton.interactable = canActivateMore; + } + + if (deactivateButton != null) + deactivateButton.gameObject.SetActive(isActive); + + if (background != null) + background.color = isActive ? activeColor : inactiveColor; } - - if (deactivateButton != null) - deactivateButton.gameObject.SetActive(isActive); - - if (background != null) - background.color = isActive ? activeColor : inactiveColor; } } -} diff --git a/Assets/Scripts/Inventory/InventoryView.cs b/Assets/Scripts/Inventory/InventoryView.cs index 269c878..2148cfe 100644 --- a/Assets/Scripts/Inventory/InventoryView.cs +++ b/Assets/Scripts/Inventory/InventoryView.cs @@ -7,73 +7,72 @@ using YachtDice.Modifiers; namespace YachtDice.Inventory { - -public sealed class InventoryView : MonoBehaviour -{ - [SerializeField] private Transform slotContainer; - [SerializeField] private InventorySlotView slotPrefab; - [SerializeField] private TMP_Text slotCountText; - [SerializeField] private Button closeButton; - - private readonly List spawnedSlots = new(); - - public event Action OnActivateClicked; - public event Action OnDeactivateClicked; - public event Action OnSellClicked; - - private void Awake() + public class InventoryView : MonoBehaviour { - if (closeButton != null) - closeButton.onClick.AddListener(Hide); - } + [SerializeField] private Transform slotContainer; + [SerializeField] private InventorySlotView slotPrefab; + [SerializeField] private TMP_Text slotCountText; + [SerializeField] private Button closeButton; - private void OnDestroy() - { - if (closeButton != null) - closeButton.onClick.RemoveListener(Hide); - } + private readonly List spawnedSlots = new(); - public void Show() => gameObject.SetActive(true); - public void Hide() => gameObject.SetActive(false); - public bool IsVisible => gameObject.activeSelf; + public event Action OnActivateClicked; + public event Action OnDeactivateClicked; + public event Action OnSellClicked; - public void Refresh(IReadOnlyList owned, int maxSlots) - { - ClearSlots(); - - int activeCount = 0; - - for (int i = 0; i < owned.Count; i++) + private void Awake() { - var runtime = owned[i]; - if (runtime.IsActive) activeCount++; - - var slot = Instantiate(slotPrefab, slotContainer); - slot.Setup(runtime, activeCount <= maxSlots); - slot.OnActivateClicked += HandleActivate; - slot.OnDeactivateClicked += HandleDeactivate; - slot.OnSellClicked += HandleSell; - spawnedSlots.Add(slot); + if (closeButton != null) + closeButton.onClick.AddListener(Hide); } - if (slotCountText != null) - slotCountText.text = $"{activeCount}/{maxSlots}"; - } - - private void ClearSlots() - { - for (int i = 0; i < spawnedSlots.Count; i++) + private void OnDestroy() { - spawnedSlots[i].OnActivateClicked -= HandleActivate; - spawnedSlots[i].OnDeactivateClicked -= HandleDeactivate; - spawnedSlots[i].OnSellClicked -= HandleSell; - Destroy(spawnedSlots[i].gameObject); + if (closeButton != null) + closeButton.onClick.RemoveListener(Hide); } - spawnedSlots.Clear(); - } - private void HandleActivate(ModifierRuntime runtime) => OnActivateClicked?.Invoke(runtime); - private void HandleDeactivate(ModifierRuntime runtime) => OnDeactivateClicked?.Invoke(runtime); - private void HandleSell(ModifierRuntime runtime) => OnSellClicked?.Invoke(runtime); -} + public void Show() => gameObject.SetActive(true); + public void Hide() => gameObject.SetActive(false); + public bool IsVisible => gameObject.activeSelf; + + public void Refresh(IReadOnlyList owned, int maxSlots) + { + ClearSlots(); + + int activeCount = 0; + + for (int i = 0; i < owned.Count; i++) + { + var runtime = owned[i]; + if (runtime.IsActive) activeCount++; + + var slot = Instantiate(slotPrefab, slotContainer); + slot.Setup(runtime, activeCount <= maxSlots); + slot.OnActivateClicked += HandleActivate; + slot.OnDeactivateClicked += HandleDeactivate; + slot.OnSellClicked += HandleSell; + spawnedSlots.Add(slot); + } + + if (slotCountText != null) + slotCountText.text = $"{activeCount}/{maxSlots}"; + } + + private void ClearSlots() + { + for (int i = 0; i < spawnedSlots.Count; i++) + { + spawnedSlots[i].OnActivateClicked -= HandleActivate; + spawnedSlots[i].OnDeactivateClicked -= HandleDeactivate; + spawnedSlots[i].OnSellClicked -= HandleSell; + Destroy(spawnedSlots[i].gameObject); + } + spawnedSlots.Clear(); + } + + private void HandleActivate(ModifierRuntime runtime) => OnActivateClicked?.Invoke(runtime); + private void HandleDeactivate(ModifierRuntime runtime) => OnDeactivateClicked?.Invoke(runtime); + private void HandleSell(ModifierRuntime runtime) => OnSellClicked?.Invoke(runtime); + } } diff --git a/Assets/Scripts/Modifiers/ModifierData.cs b/Assets/Scripts/Modifiers/ModifierData.cs index 7ad5cc9..90f6e1d 100644 --- a/Assets/Scripts/Modifiers/ModifierData.cs +++ b/Assets/Scripts/Modifiers/ModifierData.cs @@ -3,91 +3,90 @@ using YachtDice.Scoring; namespace YachtDice.Modifiers { - -[CreateAssetMenu(fileName = "NewModifier", menuName = "YachtDice/Modifier Data")] -public sealed class ModifierData : ScriptableObject -{ - [SerializeField] private string id; - [SerializeField] private string displayName; - [SerializeField] [TextArea] private string description; - [SerializeField] private ModifierRarity rarity; - [SerializeField] private int shopPrice; - [SerializeField] private int sellPrice; - [SerializeField] private Sprite icon; - - [Header("Effect")] - [SerializeField] private ModifierScope scope; - [SerializeField] private ModifierEffectType effectType; - [SerializeField] private ModifierTarget target; - [SerializeField] private float effectValue; - - [Header("Durability")] - [SerializeField] private ModifierDurability durability; - [SerializeField] private int maxUses; - - public string Id => id; - public string DisplayName => displayName; - public string Description => description; - public ModifierRarity Rarity => rarity; - public int ShopPrice => shopPrice; - public int SellPrice => sellPrice; - public Sprite Icon => icon; - public ModifierScope Scope => scope; - public ModifierEffectType EffectType => effectType; - public ModifierTarget Target => target; - public float EffectValue => effectValue; - public ModifierDurability Durability => durability; - public int MaxUses => maxUses; - - public bool IsAdditive => - effectType == ModifierEffectType.AddPerDieValue || - effectType == ModifierEffectType.AddFlatToFinalScore; - - public bool IsMultiplicative => - effectType == ModifierEffectType.MultiplyPerDieValue || - effectType == ModifierEffectType.MultiplyFinalScore; - - public bool IsCategoryLevel => - effectType == ModifierEffectType.AddPerDieValue || - effectType == ModifierEffectType.MultiplyPerDieValue; - - public bool IsFinalScoreLevel => - effectType == ModifierEffectType.AddFlatToFinalScore || - effectType == ModifierEffectType.MultiplyFinalScore; - -#if UNITY_EDITOR - public static ModifierData CreateForTest( - string id, - ModifierScope scope, - ModifierEffectType effectType, - float effectValue, - int dieValue = 0, - YachtCategory targetCategory = YachtCategory.Ones, - bool hasCategoryFilter = false, - ModifierDurability durability = ModifierDurability.Permanent, - int maxUses = 0, - int shopPrice = 100, - int sellPrice = 50) + [CreateAssetMenu(fileName = "NewModifier", menuName = "YachtDice/Modifier Data")] + public sealed class ModifierData : ScriptableObject { - var data = CreateInstance(); - data.id = id; - data.displayName = id; - data.description = id; - data.scope = scope; - data.effectType = effectType; - data.effectValue = effectValue; - data.target = new ModifierTarget + [SerializeField] private string id; + [SerializeField] private string displayName; + [SerializeField] [TextArea] private string description; + [SerializeField] private ModifierRarity rarity; + [SerializeField] private int shopPrice; + [SerializeField] private int sellPrice; + [SerializeField] private Sprite icon; + + [Header("Effect")] + [SerializeField] private ModifierScope scope; + [SerializeField] private ModifierEffectType effectType; + [SerializeField] private ModifierTarget target; + [SerializeField] private float effectValue; + + [Header("Durability")] + [SerializeField] private ModifierDurability durability; + [SerializeField] private int maxUses; + + public string Id => id; + public string DisplayName => displayName; + public string Description => description; + public ModifierRarity Rarity => rarity; + public int ShopPrice => shopPrice; + public int SellPrice => sellPrice; + public Sprite Icon => icon; + public ModifierScope Scope => scope; + public ModifierEffectType EffectType => effectType; + public ModifierTarget Target => target; + public float EffectValue => effectValue; + public ModifierDurability Durability => durability; + public int MaxUses => maxUses; + + public bool IsAdditive => + effectType == ModifierEffectType.AddPerDieValue || + effectType == ModifierEffectType.AddFlatToFinalScore; + + public bool IsMultiplicative => + effectType == ModifierEffectType.MultiplyPerDieValue || + effectType == ModifierEffectType.MultiplyFinalScore; + + public bool IsCategoryLevel => + effectType == ModifierEffectType.AddPerDieValue || + effectType == ModifierEffectType.MultiplyPerDieValue; + + public bool IsFinalScoreLevel => + effectType == ModifierEffectType.AddFlatToFinalScore || + effectType == ModifierEffectType.MultiplyFinalScore; + + #if UNITY_EDITOR + public static ModifierData CreateForTest( + string id, + ModifierScope scope, + ModifierEffectType effectType, + float effectValue, + int dieValue = 0, + YachtCategory targetCategory = YachtCategory.Ones, + bool hasCategoryFilter = false, + ModifierDurability durability = ModifierDurability.Permanent, + int maxUses = 0, + int shopPrice = 100, + int sellPrice = 50) { - DieValue = dieValue, - TargetCategory = targetCategory, - HasCategoryFilter = hasCategoryFilter - }; - data.durability = durability; - data.maxUses = maxUses; - data.shopPrice = shopPrice; - data.sellPrice = sellPrice; - return data; + var data = CreateInstance(); + data.id = id; + data.displayName = id; + data.description = id; + data.scope = scope; + data.effectType = effectType; + data.effectValue = effectValue; + data.target = new ModifierTarget + { + DieValue = dieValue, + TargetCategory = targetCategory, + HasCategoryFilter = hasCategoryFilter + }; + data.durability = durability; + data.maxUses = maxUses; + data.shopPrice = shopPrice; + data.sellPrice = sellPrice; + return data; + } + #endif } -#endif -} } diff --git a/Assets/Scripts/Modifiers/ModifierEffect.cs b/Assets/Scripts/Modifiers/ModifierEffect.cs index a59db66..7dad503 100644 --- a/Assets/Scripts/Modifiers/ModifierEffect.cs +++ b/Assets/Scripts/Modifiers/ModifierEffect.cs @@ -3,58 +3,57 @@ using YachtDice.Scoring; namespace YachtDice.Modifiers { + public delegate void ModifierHandler(ModifierData data, ref ScoreResult result); -public delegate void ModifierHandler(ModifierData data, ref ScoreResult result); - -public static class ModifierEffect -{ - private static readonly Dictionary Handlers = new() + public static class ModifierEffect { - { ModifierEffectType.AddPerDieValue, ApplyAddPerDieValue }, - { ModifierEffectType.AddFlatToFinalScore, ApplyAddFlat }, - { ModifierEffectType.MultiplyPerDieValue, ApplyMultiplyPerDieValue }, - { ModifierEffectType.MultiplyFinalScore, ApplyMultiplyFinal } - }; - - public static void Apply(ModifierData data, ref ScoreResult result) - { - if (Handlers.TryGetValue(data.EffectType, out var handler)) - handler(data, ref result); - } - - private static void ApplyAddPerDieValue(ModifierData data, ref ScoreResult result) - { - int targetValue = data.Target.DieValue; - int count = 0; - - for (int i = 0; i < result.DiceValues.Length; i++) + private static readonly Dictionary Handlers = new() { - if (targetValue == 0 || result.DiceValues[i] == targetValue) - count++; + { ModifierEffectType.AddPerDieValue, ApplyAddPerDieValue }, + { ModifierEffectType.AddFlatToFinalScore, ApplyAddFlat }, + { ModifierEffectType.MultiplyPerDieValue, ApplyMultiplyPerDieValue }, + { ModifierEffectType.MultiplyFinalScore, ApplyMultiplyFinal } + }; + + public static void Apply(ModifierData data, ref ScoreResult result) + { + if (Handlers.TryGetValue(data.EffectType, out var handler)) + handler(data, ref result); } - result.FlatBonus += (int)(data.EffectValue * count); - } - - private static void ApplyAddFlat(ModifierData data, ref ScoreResult result) - { - result.FlatBonus += (int)data.EffectValue; - } - - private static void ApplyMultiplyPerDieValue(ModifierData data, ref ScoreResult result) - { - int targetValue = data.Target.DieValue; - - for (int i = 0; i < result.DiceValues.Length; i++) + private static void ApplyAddPerDieValue(ModifierData data, ref ScoreResult result) { - if (targetValue == 0 || result.DiceValues[i] == targetValue) - result.Multiplier *= data.EffectValue; + int targetValue = data.Target.DieValue; + int count = 0; + + for (int i = 0; i < result.DiceValues.Length; i++) + { + if (targetValue == 0 || result.DiceValues[i] == targetValue) + count++; + } + + result.FlatBonus += (int)(data.EffectValue * count); + } + + private static void ApplyAddFlat(ModifierData data, ref ScoreResult result) + { + result.FlatBonus += (int)data.EffectValue; + } + + private static void ApplyMultiplyPerDieValue(ModifierData data, ref ScoreResult result) + { + int targetValue = data.Target.DieValue; + + for (int i = 0; i < result.DiceValues.Length; i++) + { + if (targetValue == 0 || result.DiceValues[i] == targetValue) + result.Multiplier *= data.EffectValue; + } + } + + private static void ApplyMultiplyFinal(ModifierData data, ref ScoreResult result) + { + result.Multiplier *= data.EffectValue; } } - - private static void ApplyMultiplyFinal(ModifierData data, ref ScoreResult result) - { - result.Multiplier *= data.EffectValue; - } -} } diff --git a/Assets/Scripts/Modifiers/ModifierEnums.cs b/Assets/Scripts/Modifiers/ModifierEnums.cs index 3d5e8d3..f49207f 100644 --- a/Assets/Scripts/Modifiers/ModifierEnums.cs +++ b/Assets/Scripts/Modifiers/ModifierEnums.cs @@ -1,30 +1,30 @@ namespace YachtDice.Modifiers { -public enum ModifierScope -{ - SelectedCategory, - AnyCategoryClosed -} + public enum ModifierScope + { + SelectedCategory, + AnyCategoryClosed + } -public enum ModifierEffectType -{ - AddPerDieValue, - AddFlatToFinalScore, - MultiplyPerDieValue, - MultiplyFinalScore -} + public enum ModifierEffectType + { + AddPerDieValue, + AddFlatToFinalScore, + MultiplyPerDieValue, + MultiplyFinalScore + } -public enum ModifierDurability -{ - Permanent, - LimitedUses -} + public enum ModifierDurability + { + Permanent, + LimitedUses + } -public enum ModifierRarity -{ - Common, - Uncommon, - Rare, - Epic -} + public enum ModifierRarity + { + Common, + Uncommon, + Rare, + Epic + } } diff --git a/Assets/Scripts/Modifiers/ModifierPipeline.cs b/Assets/Scripts/Modifiers/ModifierPipeline.cs index f90772b..8beb99e 100644 --- a/Assets/Scripts/Modifiers/ModifierPipeline.cs +++ b/Assets/Scripts/Modifiers/ModifierPipeline.cs @@ -3,65 +3,64 @@ using YachtDice.Scoring; namespace YachtDice.Modifiers { - -public static class ModifierPipeline -{ - // Application order (explicit): - // 1. Category-level additive (AddPerDieValue) - // 2. Category-level multiplicative (MultiplyPerDieValue) - // 3. Final-score additive (AddFlatToFinalScore) - // 4. Final-score multiplicative (MultiplyFinalScore) - - public static void Apply( - IReadOnlyList activeModifiers, - ref ScoreResult result, - ModifierScope currentScope) + public static class ModifierPipeline { - if (activeModifiers == null) return; + // Application order (explicit): + // 1. Category-level additive (AddPerDieValue) + // 2. Category-level multiplicative (MultiplyPerDieValue) + // 3. Final-score additive (AddFlatToFinalScore) + // 4. Final-score multiplicative (MultiplyFinalScore) - // Pass 1: Category-level additive - for (int i = 0; i < activeModifiers.Count; i++) + public static void Apply( + IReadOnlyList activeModifiers, + ref ScoreResult result, + ModifierScope currentScope) { - var mod = activeModifiers[i]; - if (!ShouldApply(mod, ref result, currentScope)) continue; - if (mod.IsCategoryLevel && mod.IsAdditive) - ModifierEffect.Apply(mod, ref result); + if (activeModifiers == null) return; + + // Pass 1: Category-level additive + for (int i = 0; i < activeModifiers.Count; i++) + { + var mod = activeModifiers[i]; + if (!ShouldApply(mod, ref result, currentScope)) continue; + if (mod.IsCategoryLevel && mod.IsAdditive) + ModifierEffect.Apply(mod, ref result); + } + + // Pass 2: Category-level multiplicative + for (int i = 0; i < activeModifiers.Count; i++) + { + var mod = activeModifiers[i]; + if (!ShouldApply(mod, ref result, currentScope)) continue; + if (mod.IsCategoryLevel && mod.IsMultiplicative) + ModifierEffect.Apply(mod, ref result); + } + + // Pass 3: Final-score additive + for (int i = 0; i < activeModifiers.Count; i++) + { + var mod = activeModifiers[i]; + if (!ShouldApply(mod, ref result, currentScope)) continue; + if (mod.IsFinalScoreLevel && mod.IsAdditive) + ModifierEffect.Apply(mod, ref result); + } + + // Pass 4: Final-score multiplicative + for (int i = 0; i < activeModifiers.Count; i++) + { + var mod = activeModifiers[i]; + if (!ShouldApply(mod, ref result, currentScope)) continue; + if (mod.IsFinalScoreLevel && mod.IsMultiplicative) + ModifierEffect.Apply(mod, ref result); + } } - // Pass 2: Category-level multiplicative - for (int i = 0; i < activeModifiers.Count; i++) + private static bool ShouldApply(ModifierData mod, ref ScoreResult result, ModifierScope currentScope) { - var mod = activeModifiers[i]; - if (!ShouldApply(mod, ref result, currentScope)) continue; - if (mod.IsCategoryLevel && mod.IsMultiplicative) - ModifierEffect.Apply(mod, ref result); - } - - // Pass 3: Final-score additive - for (int i = 0; i < activeModifiers.Count; i++) - { - var mod = activeModifiers[i]; - if (!ShouldApply(mod, ref result, currentScope)) continue; - if (mod.IsFinalScoreLevel && mod.IsAdditive) - ModifierEffect.Apply(mod, ref result); - } - - // Pass 4: Final-score multiplicative - for (int i = 0; i < activeModifiers.Count; i++) - { - var mod = activeModifiers[i]; - if (!ShouldApply(mod, ref result, currentScope)) continue; - if (mod.IsFinalScoreLevel && mod.IsMultiplicative) - ModifierEffect.Apply(mod, ref result); + if (mod == null) return false; + if (mod.Scope != currentScope) return false; + if (mod.Target.HasCategoryFilter && mod.Target.TargetCategory != result.Category) return false; + return true; } } - - private static bool ShouldApply(ModifierData mod, ref ScoreResult result, ModifierScope currentScope) - { - if (mod == null) return false; - if (mod.Scope != currentScope) return false; - if (mod.Target.HasCategoryFilter && mod.Target.TargetCategory != result.Category) return false; - return true; - } -} } diff --git a/Assets/Scripts/Modifiers/ModifierRuntime.cs b/Assets/Scripts/Modifiers/ModifierRuntime.cs index c89fb4a..b559663 100644 --- a/Assets/Scripts/Modifiers/ModifierRuntime.cs +++ b/Assets/Scripts/Modifiers/ModifierRuntime.cs @@ -2,37 +2,36 @@ using System; namespace YachtDice.Modifiers { - -[Serializable] -public sealed class ModifierRuntime -{ - public string ModifierId; - public bool IsActive; - public int RemainingUses; - - [NonSerialized] public ModifierData Data; - - public bool IsExpired => Data != null && - Data.Durability == ModifierDurability.LimitedUses && - RemainingUses <= 0; - - public void ConsumeUse() + [Serializable] + public class ModifierRuntime { - if (Data == null) return; - if (Data.Durability != ModifierDurability.LimitedUses) return; + public string ModifierId; + public bool IsActive; + public int RemainingUses; - RemainingUses--; - } + [NonSerialized] public ModifierData Data; - public static ModifierRuntime Create(ModifierData data) - { - return new ModifierRuntime + public bool IsExpired => Data != null && + Data.Durability == ModifierDurability.LimitedUses && + RemainingUses <= 0; + + public void ConsumeUse() { - ModifierId = data.Id, - IsActive = false, - RemainingUses = data.Durability == ModifierDurability.LimitedUses ? data.MaxUses : -1, - Data = data - }; + if (Data == null) return; + if (Data.Durability != ModifierDurability.LimitedUses) return; + + RemainingUses--; + } + + public static ModifierRuntime Create(ModifierData data) + { + return new ModifierRuntime + { + ModifierId = data.Id, + IsActive = false, + RemainingUses = data.Durability == ModifierDurability.LimitedUses ? data.MaxUses : -1, + Data = data + }; + } } } -} diff --git a/Assets/Scripts/Modifiers/ModifierTarget.cs b/Assets/Scripts/Modifiers/ModifierTarget.cs index 72e399b..8a9d34a 100644 --- a/Assets/Scripts/Modifiers/ModifierTarget.cs +++ b/Assets/Scripts/Modifiers/ModifierTarget.cs @@ -4,18 +4,17 @@ using YachtDice.Scoring; namespace YachtDice.Modifiers { + [Serializable] + public struct ModifierTarget + { + [Tooltip("Die face value (1-6). 0 = any/all dice.")] + [Range(0, 6)] + public int DieValue; -[Serializable] -public struct ModifierTarget -{ - [Tooltip("Die face value (1-6). 0 = any/all dice.")] - [Range(0, 6)] - public int DieValue; + [Tooltip("Category this modifier targets.")] + public YachtCategory TargetCategory; - [Tooltip("Category this modifier targets.")] - public YachtCategory TargetCategory; - - [Tooltip("If true, TargetCategory is used as a filter.")] - public bool HasCategoryFilter; -} + [Tooltip("If true, TargetCategory is used as a filter.")] + public bool HasCategoryFilter; + } } diff --git a/Assets/Scripts/Persistence/SaveData.cs b/Assets/Scripts/Persistence/SaveData.cs index 43365b9..ddcea33 100644 --- a/Assets/Scripts/Persistence/SaveData.cs +++ b/Assets/Scripts/Persistence/SaveData.cs @@ -3,20 +3,19 @@ using System.Collections.Generic; namespace YachtDice.Persistence { + [Serializable] + public sealed class SaveData + { + public int Version = 1; + public int Currency; + public List OwnedModifiers = new(); + } -[Serializable] -public sealed class SaveData -{ - public int Version = 1; - public int Currency; - public List OwnedModifiers = new(); -} - -[Serializable] -public sealed class ModifierSaveEntry -{ - public string ModifierId; - public bool IsActive; - public int RemainingUses; -} + [Serializable] + public sealed class ModifierSaveEntry + { + public string ModifierId; + public bool IsActive; + public int RemainingUses; + } } diff --git a/Assets/Scripts/Persistence/SaveSystem.cs b/Assets/Scripts/Persistence/SaveSystem.cs index 43c3b5a..cf9eee1 100644 --- a/Assets/Scripts/Persistence/SaveSystem.cs +++ b/Assets/Scripts/Persistence/SaveSystem.cs @@ -3,44 +3,43 @@ using UnityEngine; namespace YachtDice.Persistence { - -public static class SaveSystem -{ - private const string SaveKey = "YachtDice_SaveData"; - - public static void Save(SaveData data) + public static class SaveSystem { - string json = JsonConvert.SerializeObject(data, Formatting.Indented); - PlayerPrefs.SetString(SaveKey, json); - PlayerPrefs.Save(); - } + private const string SaveKey = "YachtDice_SaveData"; - public static SaveData Load() - { - if (!PlayerPrefs.HasKey(SaveKey)) - return new SaveData(); - - string json = PlayerPrefs.GetString(SaveKey); - - try + public static void Save(SaveData data) { - return JsonConvert.DeserializeObject(json) ?? new SaveData(); + string json = JsonConvert.SerializeObject(data, Formatting.Indented); + PlayerPrefs.SetString(SaveKey, json); + PlayerPrefs.Save(); } - catch (JsonException e) + + public static SaveData Load() { - Debug.LogWarning($"Failed to deserialize save data, returning default: {e.Message}"); - return new SaveData(); + if (!PlayerPrefs.HasKey(SaveKey)) + return new SaveData(); + + string json = PlayerPrefs.GetString(SaveKey); + + try + { + return JsonConvert.DeserializeObject(json) ?? new SaveData(); + } + catch (JsonException e) + { + Debug.LogWarning($"Failed to deserialize save data, returning default: {e.Message}"); + return new SaveData(); + } + } + + public static void Delete() + { + PlayerPrefs.DeleteKey(SaveKey); + } + + public static bool HasSave() + { + return PlayerPrefs.HasKey(SaveKey); } } - - public static void Delete() - { - PlayerPrefs.DeleteKey(SaveKey); - } - - public static bool HasSave() - { - return PlayerPrefs.HasKey(SaveKey); - } -} } diff --git a/Assets/Scripts/Scoring/CategoryScorer.cs b/Assets/Scripts/Scoring/CategoryScorer.cs index 6763ad5..0b66d3c 100644 --- a/Assets/Scripts/Scoring/CategoryScorer.cs +++ b/Assets/Scripts/Scoring/CategoryScorer.cs @@ -2,82 +2,81 @@ using System; namespace YachtDice.Scoring { - -public static class CategoryScorer -{ - public static int Calculate(int[] dice, YachtCategory category) + public static class CategoryScorer { - if (dice == null || dice.Length != 5) - throw new ArgumentException("Exactly 5 dice values required."); - - return category switch + public static int Calculate(int[] dice, YachtCategory category) { - YachtCategory.Ones => SumOfValue(dice, 1), - YachtCategory.Twos => SumOfValue(dice, 2), - YachtCategory.Threes => SumOfValue(dice, 3), - YachtCategory.Fours => SumOfValue(dice, 4), - YachtCategory.Fives => SumOfValue(dice, 5), - YachtCategory.Sixes => SumOfValue(dice, 6), - YachtCategory.ThreeOfAKind => NOfAKind(dice, 3) ? Sum(dice) : 0, - YachtCategory.FourOfAKind => NOfAKind(dice, 4) ? Sum(dice) : 0, - YachtCategory.FullHouse => IsFullHouse(dice) ? 25 : 0, - YachtCategory.SmallStraight => HasStraightRun(dice, 4) ? 30 : 0, - YachtCategory.LargeStraight => HasStraightRun(dice, 5) ? 40 : 0, - YachtCategory.Yacht => NOfAKind(dice, 5) ? 50 : 0, - YachtCategory.Chance => Sum(dice), - _ => 0 - }; - } + if (dice == null || dice.Length != 5) + throw new ArgumentException("Exactly 5 dice values required."); - private static int SumOfValue(int[] dice, int target) - { - int sum = 0; - for (int i = 0; i < dice.Length; i++) - if (dice[i] == target) sum += target; - return sum; - } - - private static int Sum(int[] dice) - { - int sum = 0; - for (int i = 0; i < dice.Length; i++) sum += dice[i]; - return sum; - } - - private static bool NOfAKind(int[] dice, int n) - { - int[] counts = new int[7]; - for (int i = 0; i < dice.Length; i++) counts[dice[i]]++; - for (int v = 1; v <= 6; v++) - if (counts[v] >= n) return true; - return false; - } - - private static bool IsFullHouse(int[] dice) - { - int[] counts = new int[7]; - for (int i = 0; i < dice.Length; i++) counts[dice[i]]++; - bool hasTwo = false, hasThree = false; - for (int v = 1; v <= 6; v++) - { - if (counts[v] == 2) hasTwo = true; - if (counts[v] == 3) hasThree = true; + return category switch + { + YachtCategory.Ones => SumOfValue(dice, 1), + YachtCategory.Twos => SumOfValue(dice, 2), + YachtCategory.Threes => SumOfValue(dice, 3), + YachtCategory.Fours => SumOfValue(dice, 4), + YachtCategory.Fives => SumOfValue(dice, 5), + YachtCategory.Sixes => SumOfValue(dice, 6), + YachtCategory.ThreeOfAKind => NOfAKind(dice, 3) ? Sum(dice) : 0, + YachtCategory.FourOfAKind => NOfAKind(dice, 4) ? Sum(dice) : 0, + YachtCategory.FullHouse => IsFullHouse(dice) ? 25 : 0, + YachtCategory.SmallStraight => HasStraightRun(dice, 4) ? 30 : 0, + YachtCategory.LargeStraight => HasStraightRun(dice, 5) ? 40 : 0, + YachtCategory.Yacht => NOfAKind(dice, 5) ? 50 : 0, + YachtCategory.Chance => Sum(dice), + _ => 0 + }; } - return hasTwo && hasThree; - } - private static bool HasStraightRun(int[] dice, int runLength) - { - bool[] present = new bool[7]; - for (int i = 0; i < dice.Length; i++) present[dice[i]] = true; - - int consecutive = 0; - for (int v = 1; v <= 6; v++) + private static int SumOfValue(int[] dice, int target) { - consecutive = present[v] ? consecutive + 1 : 0; - if (consecutive >= runLength) return true; + int sum = 0; + for (int i = 0; i < dice.Length; i++) + if (dice[i] == target) sum += target; + return sum; + } + + private static int Sum(int[] dice) + { + int sum = 0; + for (int i = 0; i < dice.Length; i++) sum += dice[i]; + return sum; + } + + private static bool NOfAKind(int[] dice, int n) + { + int[] counts = new int[7]; + for (int i = 0; i < dice.Length; i++) counts[dice[i]]++; + for (int v = 1; v <= 6; v++) + if (counts[v] >= n) return true; + return false; + } + + private static bool IsFullHouse(int[] dice) + { + int[] counts = new int[7]; + for (int i = 0; i < dice.Length; i++) counts[dice[i]]++; + bool hasTwo = false, hasThree = false; + for (int v = 1; v <= 6; v++) + { + if (counts[v] == 2) hasTwo = true; + if (counts[v] == 3) hasThree = true; + } + return hasTwo && hasThree; + } + + private static bool HasStraightRun(int[] dice, int runLength) + { + bool[] present = new bool[7]; + for (int i = 0; i < dice.Length; i++) present[dice[i]] = true; + + int consecutive = 0; + for (int v = 1; v <= 6; v++) + { + consecutive = present[v] ? consecutive + 1 : 0; + if (consecutive >= runLength) return true; + } + return false; } - return false; } } -} diff --git a/Assets/Scripts/Scoring/ScoreResult.cs b/Assets/Scripts/Scoring/ScoreResult.cs index 154d30f..3f34fe9 100644 --- a/Assets/Scripts/Scoring/ScoreResult.cs +++ b/Assets/Scripts/Scoring/ScoreResult.cs @@ -3,28 +3,27 @@ using UnityEngine; namespace YachtDice.Scoring { - -[Serializable] -public struct ScoreResult -{ - public int BaseScore; - public int FlatBonus; - public float Multiplier; - public int[] DiceValues; - public YachtCategory Category; - - public int FinalScore => Mathf.FloorToInt((BaseScore + FlatBonus) * Multiplier); - - public static ScoreResult Create(int baseScore, int[] diceValues, YachtCategory category) + [Serializable] + public struct ScoreResult { - return new ScoreResult + public int BaseScore; + public int FlatBonus; + public float Multiplier; + public int[] DiceValues; + public YachtCategory Category; + + public int FinalScore => Mathf.FloorToInt((BaseScore + FlatBonus) * Multiplier); + + public static ScoreResult Create(int baseScore, int[] diceValues, YachtCategory category) { - BaseScore = baseScore, - FlatBonus = 0, - Multiplier = 1f, - DiceValues = diceValues, - Category = category - }; + return new ScoreResult + { + BaseScore = baseScore, + FlatBonus = 0, + Multiplier = 1f, + DiceValues = diceValues, + Category = category + }; + } } } -} diff --git a/Assets/Scripts/Scoring/ScoringSystem.cs b/Assets/Scripts/Scoring/ScoringSystem.cs index 090ae63..d9eee18 100644 --- a/Assets/Scripts/Scoring/ScoringSystem.cs +++ b/Assets/Scripts/Scoring/ScoringSystem.cs @@ -5,85 +5,84 @@ using YachtDice.Modifiers; namespace YachtDice.Scoring { - -public sealed class ScoringSystem : MonoBehaviour -{ - public event Action OnCategoryScored; - public event Action OnAllCategoriesScored; - public event Action OnCategoryConfirmed; - - private readonly Dictionary scorecard = new(); - private readonly HashSet usedCategories = new(); - private List activeModifierData = new(); - - public bool IsCategoryUsed(YachtCategory category) => usedCategories.Contains(category); - - public int GetCategoryScore(YachtCategory category) + public class ScoringSystem : MonoBehaviour { - return scorecard.TryGetValue(category, out int score) ? score : -1; - } + public event Action OnCategoryScored; + public event Action OnAllCategoriesScored; + public event Action OnCategoryConfirmed; - public int TotalScore - { - get + private readonly Dictionary scorecard = new(); + private readonly HashSet usedCategories = new(); + private List activeModifierData = new(); + + public bool IsCategoryUsed(YachtCategory category) => usedCategories.Contains(category); + + public int GetCategoryScore(YachtCategory category) { - int total = 0; - foreach (var kvp in scorecard) total += kvp.Value; - return total; + return scorecard.TryGetValue(category, out int score) ? score : -1; + } + + public int TotalScore + { + get + { + int total = 0; + foreach (var kvp in scorecard) total += kvp.Value; + return total; + } + } + + public int CategoriesFilledCount => usedCategories.Count; + + public int TotalCategoryCount => Enum.GetValues(typeof(YachtCategory)).Length; + + public bool IsComplete => CategoriesFilledCount >= TotalCategoryCount; + + public void SetActiveModifiers(List modifiers) + { + activeModifierData = modifiers ?? new List(); + } + + public IReadOnlyList ActiveModifiers => activeModifierData; + + public ScoreResult PreviewScore(int[] diceValues, YachtCategory category) + { + int baseScore = CategoryScorer.Calculate(diceValues, category); + ScoreResult result = ScoreResult.Create(baseScore, diceValues, category); + + ModifierPipeline.Apply(activeModifierData, ref result, ModifierScope.SelectedCategory); + + return result; + } + + public ScoreResult ScoreCategory(int[] diceValues, YachtCategory category) + { + if (usedCategories.Contains(category)) + throw new InvalidOperationException($"Category {category} has already been scored."); + + int baseScore = CategoryScorer.Calculate(diceValues, category); + ScoreResult result = ScoreResult.Create(baseScore, diceValues, category); + + ModifierPipeline.Apply(activeModifierData, ref result, ModifierScope.SelectedCategory); + ModifierPipeline.Apply(activeModifierData, ref result, ModifierScope.AnyCategoryClosed); + + int finalScore = result.FinalScore; + scorecard[category] = finalScore; + usedCategories.Add(category); + + OnCategoryScored?.Invoke(category, finalScore); + OnCategoryConfirmed?.Invoke(category, result); + + if (IsComplete) + OnAllCategoriesScored?.Invoke(TotalScore); + + return result; + } + + public void ResetScorecard() + { + scorecard.Clear(); + usedCategories.Clear(); } } - - public int CategoriesFilledCount => usedCategories.Count; - - public int TotalCategoryCount => Enum.GetValues(typeof(YachtCategory)).Length; - - public bool IsComplete => CategoriesFilledCount >= TotalCategoryCount; - - public void SetActiveModifiers(List modifiers) - { - activeModifierData = modifiers ?? new List(); - } - - public IReadOnlyList ActiveModifiers => activeModifierData; - - public ScoreResult PreviewScore(int[] diceValues, YachtCategory category) - { - int baseScore = CategoryScorer.Calculate(diceValues, category); - ScoreResult result = ScoreResult.Create(baseScore, diceValues, category); - - ModifierPipeline.Apply(activeModifierData, ref result, ModifierScope.SelectedCategory); - - return result; - } - - public ScoreResult ScoreCategory(int[] diceValues, YachtCategory category) - { - if (usedCategories.Contains(category)) - throw new InvalidOperationException($"Category {category} has already been scored."); - - int baseScore = CategoryScorer.Calculate(diceValues, category); - ScoreResult result = ScoreResult.Create(baseScore, diceValues, category); - - ModifierPipeline.Apply(activeModifierData, ref result, ModifierScope.SelectedCategory); - ModifierPipeline.Apply(activeModifierData, ref result, ModifierScope.AnyCategoryClosed); - - int finalScore = result.FinalScore; - scorecard[category] = finalScore; - usedCategories.Add(category); - - OnCategoryScored?.Invoke(category, finalScore); - OnCategoryConfirmed?.Invoke(category, result); - - if (IsComplete) - OnAllCategoriesScored?.Invoke(TotalScore); - - return result; - } - - public void ResetScorecard() - { - scorecard.Clear(); - usedCategories.Clear(); - } -} } diff --git a/Assets/Scripts/Scoring/YachtCategory.cs b/Assets/Scripts/Scoring/YachtCategory.cs index 030b132..9f7bacf 100644 --- a/Assets/Scripts/Scoring/YachtCategory.cs +++ b/Assets/Scripts/Scoring/YachtCategory.cs @@ -1,22 +1,22 @@ namespace YachtDice.Scoring { -public enum YachtCategory -{ - // Upper Section - Ones, - Twos, - Threes, - Fours, - Fives, - Sixes, + public enum YachtCategory + { + // Upper Section + Ones, + Twos, + Threes, + Fours, + Fives, + Sixes, - // Lower Section - ThreeOfAKind, - FourOfAKind, - FullHouse, - SmallStraight, - LargeStraight, - Yacht, - Chance -} + // Lower Section + ThreeOfAKind, + FourOfAKind, + FullHouse, + SmallStraight, + LargeStraight, + Yacht, + Chance + } } diff --git a/Assets/Scripts/Shop/ShopCatalog.cs b/Assets/Scripts/Shop/ShopCatalog.cs index 0447cb7..f4f8e21 100644 --- a/Assets/Scripts/Shop/ShopCatalog.cs +++ b/Assets/Scripts/Shop/ShopCatalog.cs @@ -4,22 +4,21 @@ using YachtDice.Modifiers; namespace YachtDice.Shop { - -[CreateAssetMenu(fileName = "ShopCatalog", menuName = "YachtDice/Shop Catalog")] -public sealed class ShopCatalog : ScriptableObject -{ - [SerializeField] private List availableModifiers = new(); - - public IReadOnlyList AvailableModifiers => availableModifiers; - - public ModifierData FindById(string id) + [CreateAssetMenu(fileName = "ShopCatalog", menuName = "YachtDice/Shop Catalog")] + public sealed class ShopCatalog : ScriptableObject { - for (int i = 0; i < availableModifiers.Count; i++) + [SerializeField] private List availableModifiers = new(); + + public IReadOnlyList AvailableModifiers => availableModifiers; + + public ModifierData FindById(string id) { - if (availableModifiers[i] != null && availableModifiers[i].Id == id) - return availableModifiers[i]; + for (int i = 0; i < availableModifiers.Count; i++) + { + if (availableModifiers[i] != null && availableModifiers[i].Id == id) + return availableModifiers[i]; + } + return null; } - return null; } } -} diff --git a/Assets/Scripts/Shop/ShopController.cs b/Assets/Scripts/Shop/ShopController.cs index d0bbb97..4f5f4c8 100644 --- a/Assets/Scripts/Shop/ShopController.cs +++ b/Assets/Scripts/Shop/ShopController.cs @@ -4,58 +4,57 @@ using YachtDice.Modifiers; namespace YachtDice.Shop { - -public sealed class ShopController : MonoBehaviour -{ - [SerializeField] private ShopCatalog catalog; - [SerializeField] private ShopView shopView; - [SerializeField] private CurrencyBank currencyBank; - - private ShopModel model; - - public ShopCatalog Catalog => catalog; - - public void Initialize(ShopModel shopModel) + public class ShopController : MonoBehaviour { - model = shopModel; + [SerializeField] private ShopCatalog catalog; + [SerializeField] private ShopView shopView; + [SerializeField] private CurrencyBank currencyBank; - shopView.OnBuyClicked += HandleBuyClicked; + private ShopModel model; - if (currencyBank != null) - currencyBank.OnBalanceChanged += HandleCurrencyChanged; + public ShopCatalog Catalog => catalog; - model.OnItemPurchased += HandleItemPurchased; + public void Initialize(ShopModel shopModel) + { + model = shopModel; - shopView.Populate(catalog.AvailableModifiers, model); - shopView.UpdateCurrencyDisplay(currencyBank != null ? currencyBank.Balance : 0); - } + shopView.OnBuyClicked += HandleBuyClicked; - private void OnDestroy() - { - if (shopView != null) - shopView.OnBuyClicked -= HandleBuyClicked; + if (currencyBank != null) + currencyBank.OnBalanceChanged += HandleCurrencyChanged; - if (currencyBank != null) - currencyBank.OnBalanceChanged -= HandleCurrencyChanged; + model.OnItemPurchased += HandleItemPurchased; - if (model != null) - model.OnItemPurchased -= HandleItemPurchased; - } + shopView.Populate(catalog.AvailableModifiers, model); + shopView.UpdateCurrencyDisplay(currencyBank != null ? currencyBank.Balance : 0); + } - private void HandleBuyClicked(ModifierData data) - { - model.TryPurchase(data); - } + private void OnDestroy() + { + if (shopView != null) + shopView.OnBuyClicked -= HandleBuyClicked; - private void HandleCurrencyChanged(int newBalance) - { - shopView.UpdateCurrencyDisplay(newBalance); - shopView.RefreshStates(catalog.AvailableModifiers, model); - } + if (currencyBank != null) + currencyBank.OnBalanceChanged -= HandleCurrencyChanged; - private void HandleItemPurchased(ModifierData data) - { - shopView.RefreshStates(catalog.AvailableModifiers, model); + if (model != null) + model.OnItemPurchased -= HandleItemPurchased; + } + + private void HandleBuyClicked(ModifierData data) + { + model.TryPurchase(data); + } + + private void HandleCurrencyChanged(int newBalance) + { + shopView.UpdateCurrencyDisplay(newBalance); + shopView.RefreshStates(catalog.AvailableModifiers, model); + } + + private void HandleItemPurchased(ModifierData data) + { + shopView.RefreshStates(catalog.AvailableModifiers, model); + } } } -} diff --git a/Assets/Scripts/Shop/ShopItemView.cs b/Assets/Scripts/Shop/ShopItemView.cs index c2be2ab..05d5e55 100644 --- a/Assets/Scripts/Shop/ShopItemView.cs +++ b/Assets/Scripts/Shop/ShopItemView.cs @@ -6,87 +6,86 @@ using YachtDice.Modifiers; namespace YachtDice.Shop { - -public sealed class ShopItemView : MonoBehaviour -{ - [SerializeField] private Image iconImage; - [SerializeField] private TMP_Text nameText; - [SerializeField] private TMP_Text descriptionText; - [SerializeField] private TMP_Text priceText; - [SerializeField] private TMP_Text rarityText; - [SerializeField] private Button buyButton; - [SerializeField] private TMP_Text buyButtonText; - [SerializeField] private Image background; - - [Header("Rarity Colors")] - [SerializeField] private Color commonColor = Color.white; - [SerializeField] private Color uncommonColor = new(0.4f, 0.8f, 0.4f); - [SerializeField] private Color rareColor = new(0.4f, 0.6f, 1f); - [SerializeField] private Color epicColor = new(0.8f, 0.4f, 1f); - - private ModifierData data; - - public event Action OnBuyClicked; - - private void Awake() + public class ShopItemView : MonoBehaviour { - if (buyButton != null) - buyButton.onClick.AddListener(() => OnBuyClicked?.Invoke(data)); - } + [SerializeField] private Image iconImage; + [SerializeField] private TMP_Text nameText; + [SerializeField] private TMP_Text descriptionText; + [SerializeField] private TMP_Text priceText; + [SerializeField] private TMP_Text rarityText; + [SerializeField] private Button buyButton; + [SerializeField] private TMP_Text buyButtonText; + [SerializeField] private Image background; - public void Setup(ModifierData modifierData, ShopItemState state) - { - data = modifierData; + [Header("Rarity Colors")] + [SerializeField] private Color commonColor = Color.white; + [SerializeField] private Color uncommonColor = new(0.4f, 0.8f, 0.4f); + [SerializeField] private Color rareColor = new(0.4f, 0.6f, 1f); + [SerializeField] private Color epicColor = new(0.8f, 0.4f, 1f); - if (nameText != null) nameText.text = data.DisplayName; - if (descriptionText != null) descriptionText.text = data.Description; - if (priceText != null) priceText.text = data.ShopPrice.ToString(); - if (iconImage != null && data.Icon != null) iconImage.sprite = data.Icon; + private ModifierData data; - if (rarityText != null) + public event Action OnBuyClicked; + + private void Awake() { - rarityText.text = data.Rarity.ToString(); - rarityText.color = GetRarityColor(data.Rarity); + if (buyButton != null) + buyButton.onClick.AddListener(() => OnBuyClicked?.Invoke(data)); } - SetState(state); - } - - public void SetState(ShopItemState state) - { - if (buyButton == null) return; - - switch (state) + public void Setup(ModifierData modifierData, ShopItemState state) { - case ShopItemState.Available: - buyButton.interactable = true; - if (buyButtonText != null) buyButtonText.text = "Buy"; - break; - case ShopItemState.RepurchaseAvailable: - buyButton.interactable = true; - if (buyButtonText != null) buyButtonText.text = "Buy"; - break; - case ShopItemState.TooExpensive: - buyButton.interactable = false; - if (buyButtonText != null) buyButtonText.text = "Buy"; - break; - case ShopItemState.Owned: - buyButton.interactable = false; - if (buyButtonText != null) buyButtonText.text = "Owned"; - break; + data = modifierData; + + if (nameText != null) nameText.text = data.DisplayName; + if (descriptionText != null) descriptionText.text = data.Description; + if (priceText != null) priceText.text = data.ShopPrice.ToString(); + if (iconImage != null && data.Icon != null) iconImage.sprite = data.Icon; + + if (rarityText != null) + { + rarityText.text = data.Rarity.ToString(); + rarityText.color = GetRarityColor(data.Rarity); + } + + SetState(state); + } + + public void SetState(ShopItemState state) + { + if (buyButton == null) return; + + switch (state) + { + case ShopItemState.Available: + buyButton.interactable = true; + if (buyButtonText != null) buyButtonText.text = "Buy"; + break; + case ShopItemState.RepurchaseAvailable: + buyButton.interactable = true; + if (buyButtonText != null) buyButtonText.text = "Buy"; + break; + case ShopItemState.TooExpensive: + buyButton.interactable = false; + if (buyButtonText != null) buyButtonText.text = "Buy"; + break; + case ShopItemState.Owned: + buyButton.interactable = false; + if (buyButtonText != null) buyButtonText.text = "Owned"; + break; + } + } + + private Color GetRarityColor(ModifierRarity rarity) + { + return rarity switch + { + ModifierRarity.Common => commonColor, + ModifierRarity.Uncommon => uncommonColor, + ModifierRarity.Rare => rareColor, + ModifierRarity.Epic => epicColor, + _ => commonColor + }; } } - - private Color GetRarityColor(ModifierRarity rarity) - { - return rarity switch - { - ModifierRarity.Common => commonColor, - ModifierRarity.Uncommon => uncommonColor, - ModifierRarity.Rare => rareColor, - ModifierRarity.Epic => epicColor, - _ => commonColor - }; - } -} } diff --git a/Assets/Scripts/Shop/ShopModel.cs b/Assets/Scripts/Shop/ShopModel.cs index f8a56c9..102d274 100644 --- a/Assets/Scripts/Shop/ShopModel.cs +++ b/Assets/Scripts/Shop/ShopModel.cs @@ -6,80 +6,79 @@ using YachtDice.Modifiers; namespace YachtDice.Shop { - -public sealed class ShopModel -{ - private readonly CurrencyBank currencyBank; - private readonly InventoryModel inventoryModel; - private readonly HashSet purchasedPermanentIds = new(); - - public event Action OnItemPurchased; - - public ShopModel(CurrencyBank currencyBank, InventoryModel inventoryModel) + public class ShopModel { - this.currencyBank = currencyBank; - this.inventoryModel = inventoryModel; + private readonly CurrencyBank currencyBank; + private readonly InventoryModel inventoryModel; + private readonly HashSet purchasedPermanentIds = new(); + + public event Action OnItemPurchased; + + public ShopModel(CurrencyBank currencyBank, InventoryModel inventoryModel) + { + this.currencyBank = currencyBank; + this.inventoryModel = inventoryModel; + } + + public bool CanPurchase(ModifierData modifier) + { + if (modifier == null) return false; + if (!currencyBank.CanAfford(modifier.ShopPrice)) return false; + + if (modifier.Durability == ModifierDurability.Permanent && + purchasedPermanentIds.Contains(modifier.Id)) + return false; + + return true; + } + + public bool TryPurchase(ModifierData modifier) + { + if (!CanPurchase(modifier)) return false; + + if (!currencyBank.Spend(modifier.ShopPrice)) return false; + + if (modifier.Durability == ModifierDurability.Permanent) + purchasedPermanentIds.Add(modifier.Id); + + inventoryModel.AddModifier(modifier); + OnItemPurchased?.Invoke(modifier); + return true; + } + + public bool IsPermanentOwned(string modifierId) => purchasedPermanentIds.Contains(modifierId); + + public ShopItemState GetItemState(ModifierData modifier) + { + if (modifier == null) return ShopItemState.TooExpensive; + + if (modifier.Durability == ModifierDurability.Permanent && + purchasedPermanentIds.Contains(modifier.Id)) + return ShopItemState.Owned; + + if (!currencyBank.CanAfford(modifier.ShopPrice)) + return ShopItemState.TooExpensive; + + return modifier.Durability == ModifierDurability.LimitedUses + ? ShopItemState.RepurchaseAvailable + : ShopItemState.Available; + } + + public void LoadPurchasedPermanentIds(IEnumerable ids) + { + purchasedPermanentIds.Clear(); + if (ids != null) + foreach (var id in ids) purchasedPermanentIds.Add(id); + } + + public HashSet GetPurchasedPermanentIds() => new(purchasedPermanentIds); } - public bool CanPurchase(ModifierData modifier) + public enum ShopItemState { - if (modifier == null) return false; - if (!currencyBank.CanAfford(modifier.ShopPrice)) return false; - - if (modifier.Durability == ModifierDurability.Permanent && - purchasedPermanentIds.Contains(modifier.Id)) - return false; - - return true; + Available, + TooExpensive, + Owned, + RepurchaseAvailable } - - public bool TryPurchase(ModifierData modifier) - { - if (!CanPurchase(modifier)) return false; - - if (!currencyBank.Spend(modifier.ShopPrice)) return false; - - if (modifier.Durability == ModifierDurability.Permanent) - purchasedPermanentIds.Add(modifier.Id); - - inventoryModel.AddModifier(modifier); - OnItemPurchased?.Invoke(modifier); - return true; - } - - public bool IsPermanentOwned(string modifierId) => purchasedPermanentIds.Contains(modifierId); - - public ShopItemState GetItemState(ModifierData modifier) - { - if (modifier == null) return ShopItemState.TooExpensive; - - if (modifier.Durability == ModifierDurability.Permanent && - purchasedPermanentIds.Contains(modifier.Id)) - return ShopItemState.Owned; - - if (!currencyBank.CanAfford(modifier.ShopPrice)) - return ShopItemState.TooExpensive; - - return modifier.Durability == ModifierDurability.LimitedUses - ? ShopItemState.RepurchaseAvailable - : ShopItemState.Available; - } - - public void LoadPurchasedPermanentIds(IEnumerable ids) - { - purchasedPermanentIds.Clear(); - if (ids != null) - foreach (var id in ids) purchasedPermanentIds.Add(id); - } - - public HashSet GetPurchasedPermanentIds() => new(purchasedPermanentIds); -} - -public enum ShopItemState -{ - Available, - TooExpensive, - Owned, - RepurchaseAvailable -} } diff --git a/Assets/Scripts/Shop/ShopView.cs b/Assets/Scripts/Shop/ShopView.cs index 44f3f3c..a888a62 100644 --- a/Assets/Scripts/Shop/ShopView.cs +++ b/Assets/Scripts/Shop/ShopView.cs @@ -7,76 +7,75 @@ using YachtDice.Modifiers; namespace YachtDice.Shop { - -public sealed class ShopView : MonoBehaviour -{ - [SerializeField] private Transform itemContainer; - [SerializeField] private ShopItemView itemPrefab; - [SerializeField] private TMP_Text currencyText; - [SerializeField] private Button closeButton; - - private readonly List spawnedItems = new(); - - public event Action OnBuyClicked; - - private void Awake() + public class ShopView : MonoBehaviour { - if (closeButton != null) - closeButton.onClick.AddListener(Hide); - } + [SerializeField] private Transform itemContainer; + [SerializeField] private ShopItemView itemPrefab; + [SerializeField] private TMP_Text currencyText; + [SerializeField] private Button closeButton; - private void OnDestroy() - { - if (closeButton != null) - closeButton.onClick.RemoveListener(Hide); - } + private readonly List spawnedItems = new(); - public void Show() => gameObject.SetActive(true); - public void Hide() => gameObject.SetActive(false); - public bool IsVisible => gameObject.activeSelf; + public event Action OnBuyClicked; - public void Populate(IReadOnlyList catalog, ShopModel model) - { - ClearItems(); - - for (int i = 0; i < catalog.Count; i++) + private void Awake() { - var data = catalog[i]; - if (data == null) continue; - - var item = Instantiate(itemPrefab, itemContainer); - var state = model.GetItemState(data); - item.Setup(data, state); - item.OnBuyClicked += HandleBuy; - spawnedItems.Add(item); + if (closeButton != null) + closeButton.onClick.AddListener(Hide); } - } - public void RefreshStates(IReadOnlyList catalog, ShopModel model) - { - for (int i = 0; i < spawnedItems.Count && i < catalog.Count; i++) + private void OnDestroy() { - var state = model.GetItemState(catalog[i]); - spawnedItems[i].SetState(state); + if (closeButton != null) + closeButton.onClick.RemoveListener(Hide); } - } - public void UpdateCurrencyDisplay(int currency) - { - if (currencyText != null) - currencyText.text = currency.ToString(); - } + public void Show() => gameObject.SetActive(true); + public void Hide() => gameObject.SetActive(false); + public bool IsVisible => gameObject.activeSelf; - private void ClearItems() - { - for (int i = 0; i < spawnedItems.Count; i++) + public void Populate(IReadOnlyList catalog, ShopModel model) { - spawnedItems[i].OnBuyClicked -= HandleBuy; - Destroy(spawnedItems[i].gameObject); - } - spawnedItems.Clear(); - } + ClearItems(); - private void HandleBuy(ModifierData data) => OnBuyClicked?.Invoke(data); -} + for (int i = 0; i < catalog.Count; i++) + { + var data = catalog[i]; + if (data == null) continue; + + var item = Instantiate(itemPrefab, itemContainer); + var state = model.GetItemState(data); + item.Setup(data, state); + item.OnBuyClicked += HandleBuy; + spawnedItems.Add(item); + } + } + + public void RefreshStates(IReadOnlyList catalog, ShopModel model) + { + for (int i = 0; i < spawnedItems.Count && i < catalog.Count; i++) + { + var state = model.GetItemState(catalog[i]); + spawnedItems[i].SetState(state); + } + } + + public void UpdateCurrencyDisplay(int currency) + { + if (currencyText != null) + currencyText.text = currency.ToString(); + } + + private void ClearItems() + { + for (int i = 0; i < spawnedItems.Count; i++) + { + spawnedItems[i].OnBuyClicked -= HandleBuy; + Destroy(spawnedItems[i].gameObject); + } + spawnedItems.Clear(); + } + + private void HandleBuy(ModifierData data) => OnBuyClicked?.Invoke(data); + } } diff --git a/Assets/Scripts/Tests/Editor/InventoryModelTests.cs b/Assets/Scripts/Tests/Editor/InventoryModelTests.cs index a80b586..095ac40 100644 --- a/Assets/Scripts/Tests/Editor/InventoryModelTests.cs +++ b/Assets/Scripts/Tests/Editor/InventoryModelTests.cs @@ -5,157 +5,156 @@ using YachtDice.Modifiers; namespace YachtDice.Tests { - -public sealed class InventoryModelTests -{ - private InventoryModel inventory; - - [SetUp] - public void SetUp() + public class InventoryModelTests { - inventory = new InventoryModel(3); - } + private InventoryModel inventory; - private ModifierData CreateTestData(string id = "test", - ModifierDurability durability = ModifierDurability.Permanent, int maxUses = 0) - { - return ModifierData.CreateForTest(id, ModifierScope.SelectedCategory, - ModifierEffectType.AddFlatToFinalScore, 10f, - durability: durability, maxUses: maxUses); - } - - [Test] - public void AddModifier_IncreasesCount() - { - inventory.AddModifier(CreateTestData()); - - Assert.AreEqual(1, inventory.OwnedModifiers.Count); - } - - [Test] - public void TryActivate_SucceedsWithinSlotLimit() - { - inventory.AddModifier(CreateTestData("a")); - var mod = inventory.OwnedModifiers[0]; - - bool result = inventory.TryActivate(mod); - - Assert.IsTrue(result); - Assert.IsTrue(mod.IsActive); - Assert.AreEqual(1, inventory.ActiveCount); - } - - [Test] - public void TryActivate_FailsWhenSlotsFull() - { - for (int i = 0; i < 3; i++) + [SetUp] + public void SetUp() { - inventory.AddModifier(CreateTestData($"m{i}")); - inventory.TryActivate(inventory.OwnedModifiers[i]); + inventory = new InventoryModel(3); } - inventory.AddModifier(CreateTestData("extra")); - var extra = inventory.OwnedModifiers[3]; - bool result = inventory.TryActivate(extra); + private ModifierData CreateTestData(string id = "test", + ModifierDurability durability = ModifierDurability.Permanent, int maxUses = 0) + { + return ModifierData.CreateForTest(id, ModifierScope.SelectedCategory, + ModifierEffectType.AddFlatToFinalScore, 10f, + durability: durability, maxUses: maxUses); + } - Assert.IsFalse(result); - Assert.IsFalse(extra.IsActive); - Assert.AreEqual(3, inventory.ActiveCount); - } + [Test] + public void AddModifier_IncreasesCount() + { + inventory.AddModifier(CreateTestData()); - [Test] - public void Deactivate_FreesSlot() - { - inventory.AddModifier(CreateTestData()); - var mod = inventory.OwnedModifiers[0]; - inventory.TryActivate(mod); + Assert.AreEqual(1, inventory.OwnedModifiers.Count); + } - inventory.Deactivate(mod); + [Test] + public void TryActivate_SucceedsWithinSlotLimit() + { + inventory.AddModifier(CreateTestData("a")); + var mod = inventory.OwnedModifiers[0]; - Assert.IsFalse(mod.IsActive); - Assert.AreEqual(0, inventory.ActiveCount); - } + bool result = inventory.TryActivate(mod); - [Test] - public void RemoveModifier_DeactivatesAndRemoves() - { - inventory.AddModifier(CreateTestData()); - var mod = inventory.OwnedModifiers[0]; - inventory.TryActivate(mod); + Assert.IsTrue(result); + Assert.IsTrue(mod.IsActive); + Assert.AreEqual(1, inventory.ActiveCount); + } - inventory.RemoveModifier(mod); + [Test] + public void TryActivate_FailsWhenSlotsFull() + { + for (int i = 0; i < 3; i++) + { + inventory.AddModifier(CreateTestData($"m{i}")); + inventory.TryActivate(inventory.OwnedModifiers[i]); + } - Assert.AreEqual(0, inventory.OwnedModifiers.Count); - Assert.AreEqual(0, inventory.ActiveCount); - } + inventory.AddModifier(CreateTestData("extra")); + var extra = inventory.OwnedModifiers[3]; + bool result = inventory.TryActivate(extra); - [Test] - public void ConsumeUseOnActive_DecrementsUses() - { - inventory.AddModifier(CreateTestData("ltd", ModifierDurability.LimitedUses, 3)); - var mod = inventory.OwnedModifiers[0]; - inventory.TryActivate(mod); + Assert.IsFalse(result); + Assert.IsFalse(extra.IsActive); + Assert.AreEqual(3, inventory.ActiveCount); + } - inventory.ConsumeUseOnActive(); + [Test] + public void Deactivate_FreesSlot() + { + inventory.AddModifier(CreateTestData()); + var mod = inventory.OwnedModifiers[0]; + inventory.TryActivate(mod); - Assert.AreEqual(2, mod.RemainingUses); - } + inventory.Deactivate(mod); - [Test] - public void ConsumeUseOnActive_RemovesExpired() - { - inventory.AddModifier(CreateTestData("ltd", ModifierDurability.LimitedUses, 1)); - var mod = inventory.OwnedModifiers[0]; - inventory.TryActivate(mod); + Assert.IsFalse(mod.IsActive); + Assert.AreEqual(0, inventory.ActiveCount); + } - inventory.ConsumeUseOnActive(); + [Test] + public void RemoveModifier_DeactivatesAndRemoves() + { + inventory.AddModifier(CreateTestData()); + var mod = inventory.OwnedModifiers[0]; + inventory.TryActivate(mod); - Assert.AreEqual(0, inventory.OwnedModifiers.Count); - } + inventory.RemoveModifier(mod); - [Test] - public void ConsumeUseOnActive_IgnoresPermanent() - { - inventory.AddModifier(CreateTestData("perm", ModifierDurability.Permanent)); - var mod = inventory.OwnedModifiers[0]; - inventory.TryActivate(mod); + Assert.AreEqual(0, inventory.OwnedModifiers.Count); + Assert.AreEqual(0, inventory.ActiveCount); + } - inventory.ConsumeUseOnActive(); + [Test] + public void ConsumeUseOnActive_DecrementsUses() + { + inventory.AddModifier(CreateTestData("ltd", ModifierDurability.LimitedUses, 3)); + var mod = inventory.OwnedModifiers[0]; + inventory.TryActivate(mod); - Assert.AreEqual(1, inventory.OwnedModifiers.Count); - Assert.IsTrue(mod.IsActive); - } + inventory.ConsumeUseOnActive(); - [Test] - public void GetActiveModifierData_ReturnsOnlyActive() - { - inventory.AddModifier(CreateTestData("a")); - inventory.AddModifier(CreateTestData("b")); - inventory.TryActivate(inventory.OwnedModifiers[0]); + Assert.AreEqual(2, mod.RemainingUses); + } - var active = inventory.GetActiveModifierData(); + [Test] + public void ConsumeUseOnActive_RemovesExpired() + { + inventory.AddModifier(CreateTestData("ltd", ModifierDurability.LimitedUses, 1)); + var mod = inventory.OwnedModifiers[0]; + inventory.TryActivate(mod); - Assert.AreEqual(1, active.Count); - } + inventory.ConsumeUseOnActive(); - [Test] - public void SetMaxActiveSlots_AllowsExpansion() - { - inventory.SetMaxActiveSlots(10); + Assert.AreEqual(0, inventory.OwnedModifiers.Count); + } - Assert.AreEqual(10, inventory.MaxActiveSlots); - } + [Test] + public void ConsumeUseOnActive_IgnoresPermanent() + { + inventory.AddModifier(CreateTestData("perm", ModifierDurability.Permanent)); + var mod = inventory.OwnedModifiers[0]; + inventory.TryActivate(mod); - [Test] - public void OnActiveModifiersChanged_FiredOnActivate() - { - bool fired = false; - inventory.OnActiveModifiersChanged += _ => fired = true; - inventory.AddModifier(CreateTestData()); + inventory.ConsumeUseOnActive(); - inventory.TryActivate(inventory.OwnedModifiers[0]); + Assert.AreEqual(1, inventory.OwnedModifiers.Count); + Assert.IsTrue(mod.IsActive); + } - Assert.IsTrue(fired); + [Test] + public void GetActiveModifierData_ReturnsOnlyActive() + { + inventory.AddModifier(CreateTestData("a")); + inventory.AddModifier(CreateTestData("b")); + inventory.TryActivate(inventory.OwnedModifiers[0]); + + var active = inventory.GetActiveModifierData(); + + Assert.AreEqual(1, active.Count); + } + + [Test] + public void SetMaxActiveSlots_AllowsExpansion() + { + inventory.SetMaxActiveSlots(10); + + Assert.AreEqual(10, inventory.MaxActiveSlots); + } + + [Test] + public void OnActiveModifiersChanged_FiredOnActivate() + { + bool fired = false; + inventory.OnActiveModifiersChanged += _ => fired = true; + inventory.AddModifier(CreateTestData()); + + inventory.TryActivate(inventory.OwnedModifiers[0]); + + Assert.IsTrue(fired); + } } } -} diff --git a/Assets/Scripts/Tests/Editor/ModifierEffectTests.cs b/Assets/Scripts/Tests/Editor/ModifierEffectTests.cs index cae584f..69b84a5 100644 --- a/Assets/Scripts/Tests/Editor/ModifierEffectTests.cs +++ b/Assets/Scripts/Tests/Editor/ModifierEffectTests.cs @@ -5,91 +5,90 @@ using YachtDice.Scoring; namespace YachtDice.Tests { - -public sealed class ModifierEffectTests -{ - private static ModifierData CreateData( - ModifierEffectType effectType, float effectValue, - int dieValue = 0, ModifierScope scope = ModifierScope.SelectedCategory) + public class ModifierEffectTests { - return ModifierData.CreateForTest("test", scope, effectType, effectValue, dieValue); - } + private static ModifierData CreateData( + ModifierEffectType effectType, float effectValue, + int dieValue = 0, ModifierScope scope = ModifierScope.SelectedCategory) + { + return ModifierData.CreateForTest("test", scope, effectType, effectValue, dieValue); + } - [Test] - public void AddPerDieValue_CountsMatchingDice() - { - var data = CreateData(ModifierEffectType.AddPerDieValue, 10f, dieValue: 1); - var result = ScoreResult.Create(5, new[] { 1, 1, 3, 4, 1 }, YachtCategory.Ones); + [Test] + public void AddPerDieValue_CountsMatchingDice() + { + var data = CreateData(ModifierEffectType.AddPerDieValue, 10f, dieValue: 1); + var result = ScoreResult.Create(5, new[] { 1, 1, 3, 4, 1 }, YachtCategory.Ones); - ModifierEffect.Apply(data, ref result); + ModifierEffect.Apply(data, ref result); - Assert.AreEqual(30, result.FlatBonus); - } + Assert.AreEqual(30, result.FlatBonus); + } - [Test] - public void AddPerDieValue_ZeroTarget_CountsAllDice() - { - var data = CreateData(ModifierEffectType.AddPerDieValue, 2f, dieValue: 0); - var result = ScoreResult.Create(10, new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance); + [Test] + public void AddPerDieValue_ZeroTarget_CountsAllDice() + { + var data = CreateData(ModifierEffectType.AddPerDieValue, 2f, dieValue: 0); + var result = ScoreResult.Create(10, new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance); - ModifierEffect.Apply(data, ref result); + ModifierEffect.Apply(data, ref result); - Assert.AreEqual(10, result.FlatBonus); - } + Assert.AreEqual(10, result.FlatBonus); + } - [Test] - public void AddPerDieValue_NoMatches_ZeroBonus() - { - var data = CreateData(ModifierEffectType.AddPerDieValue, 10f, dieValue: 6); - var result = ScoreResult.Create(5, new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance); + [Test] + public void AddPerDieValue_NoMatches_ZeroBonus() + { + var data = CreateData(ModifierEffectType.AddPerDieValue, 10f, dieValue: 6); + var result = ScoreResult.Create(5, new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance); - ModifierEffect.Apply(data, ref result); + ModifierEffect.Apply(data, ref result); - Assert.AreEqual(0, result.FlatBonus); - } + Assert.AreEqual(0, result.FlatBonus); + } - [Test] - public void AddFlatToFinalScore_AddsFlat() - { - var data = CreateData(ModifierEffectType.AddFlatToFinalScore, 15f); - var result = ScoreResult.Create(25, new[] { 3, 3, 2, 2, 2 }, YachtCategory.FullHouse); + [Test] + public void AddFlatToFinalScore_AddsFlat() + { + var data = CreateData(ModifierEffectType.AddFlatToFinalScore, 15f); + var result = ScoreResult.Create(25, new[] { 3, 3, 2, 2, 2 }, YachtCategory.FullHouse); - ModifierEffect.Apply(data, ref result); + ModifierEffect.Apply(data, ref result); - Assert.AreEqual(15, result.FlatBonus); - } + Assert.AreEqual(15, result.FlatBonus); + } - [Test] - public void MultiplyPerDieValue_MultipliesPerMatch() - { - var data = CreateData(ModifierEffectType.MultiplyPerDieValue, 2f, dieValue: 6); - var result = ScoreResult.Create(18, new[] { 6, 6, 6, 1, 2 }, YachtCategory.Sixes); + [Test] + public void MultiplyPerDieValue_MultipliesPerMatch() + { + var data = CreateData(ModifierEffectType.MultiplyPerDieValue, 2f, dieValue: 6); + var result = ScoreResult.Create(18, new[] { 6, 6, 6, 1, 2 }, YachtCategory.Sixes); - ModifierEffect.Apply(data, ref result); + ModifierEffect.Apply(data, ref result); - Assert.AreEqual(8f, result.Multiplier); // 1 * 2 * 2 * 2 = 8 - } + Assert.AreEqual(8f, result.Multiplier); // 1 * 2 * 2 * 2 = 8 + } - [Test] - public void MultiplyPerDieValue_NoMatches_MultiplierUnchanged() - { - var data = CreateData(ModifierEffectType.MultiplyPerDieValue, 3f, dieValue: 6); - var result = ScoreResult.Create(10, new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance); + [Test] + public void MultiplyPerDieValue_NoMatches_MultiplierUnchanged() + { + var data = CreateData(ModifierEffectType.MultiplyPerDieValue, 3f, dieValue: 6); + var result = ScoreResult.Create(10, new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance); - ModifierEffect.Apply(data, ref result); + ModifierEffect.Apply(data, ref result); - Assert.AreEqual(1f, result.Multiplier); - } + Assert.AreEqual(1f, result.Multiplier); + } - [Test] - public void MultiplyFinalScore_MultipliesOnce() - { - var data = CreateData(ModifierEffectType.MultiplyFinalScore, 1.5f); - var result = ScoreResult.Create(50, new[] { 6, 6, 6, 6, 6 }, YachtCategory.Yacht); + [Test] + public void MultiplyFinalScore_MultipliesOnce() + { + var data = CreateData(ModifierEffectType.MultiplyFinalScore, 1.5f); + var result = ScoreResult.Create(50, new[] { 6, 6, 6, 6, 6 }, YachtCategory.Yacht); - ModifierEffect.Apply(data, ref result); + ModifierEffect.Apply(data, ref result); - Assert.AreEqual(1.5f, result.Multiplier, 0.001f); + Assert.AreEqual(1.5f, result.Multiplier, 0.001f); + } } } -} diff --git a/Assets/Scripts/Tests/Editor/ModifierPipelineTests.cs b/Assets/Scripts/Tests/Editor/ModifierPipelineTests.cs index 5d888cc..6b33ce9 100644 --- a/Assets/Scripts/Tests/Editor/ModifierPipelineTests.cs +++ b/Assets/Scripts/Tests/Editor/ModifierPipelineTests.cs @@ -6,137 +6,136 @@ using YachtDice.Scoring; namespace YachtDice.Tests { - -public sealed class ModifierPipelineTests -{ - [Test] - public void Apply_AdditiveBeforeMultiplicative() + public class ModifierPipelineTests { - var addMod = ModifierData.CreateForTest("add", ModifierScope.SelectedCategory, - ModifierEffectType.AddFlatToFinalScore, 10f); - var mulMod = ModifierData.CreateForTest("mul", ModifierScope.SelectedCategory, - ModifierEffectType.MultiplyFinalScore, 2f); + [Test] + public void Apply_AdditiveBeforeMultiplicative() + { + var addMod = ModifierData.CreateForTest("add", ModifierScope.SelectedCategory, + ModifierEffectType.AddFlatToFinalScore, 10f); + var mulMod = ModifierData.CreateForTest("mul", ModifierScope.SelectedCategory, + ModifierEffectType.MultiplyFinalScore, 2f); - var modifiers = new List { mulMod, addMod }; - var result = ScoreResult.Create(20, new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance); + var modifiers = new List { mulMod, addMod }; + var result = ScoreResult.Create(20, new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance); - ModifierPipeline.Apply(modifiers, ref result, ModifierScope.SelectedCategory); + ModifierPipeline.Apply(modifiers, ref result, ModifierScope.SelectedCategory); - // (20 + 10) * 2 = 60 - Assert.AreEqual(60, result.FinalScore); - } + // (20 + 10) * 2 = 60 + Assert.AreEqual(60, result.FinalScore); + } - [Test] - public void Apply_CategoryLevelBeforeFinalScore() - { - var perDie = ModifierData.CreateForTest("perDie", ModifierScope.SelectedCategory, - ModifierEffectType.AddPerDieValue, 5f, dieValue: 1); - var flat = ModifierData.CreateForTest("flat", ModifierScope.SelectedCategory, - ModifierEffectType.AddFlatToFinalScore, 100f); + [Test] + public void Apply_CategoryLevelBeforeFinalScore() + { + var perDie = ModifierData.CreateForTest("perDie", ModifierScope.SelectedCategory, + ModifierEffectType.AddPerDieValue, 5f, dieValue: 1); + var flat = ModifierData.CreateForTest("flat", ModifierScope.SelectedCategory, + ModifierEffectType.AddFlatToFinalScore, 100f); - var modifiers = new List { flat, perDie }; - var result = ScoreResult.Create(3, new[] { 1, 1, 1, 2, 3 }, YachtCategory.Ones); + var modifiers = new List { flat, perDie }; + var result = ScoreResult.Create(3, new[] { 1, 1, 1, 2, 3 }, YachtCategory.Ones); - ModifierPipeline.Apply(modifiers, ref result, ModifierScope.SelectedCategory); + ModifierPipeline.Apply(modifiers, ref result, ModifierScope.SelectedCategory); - // FlatBonus = 15 (perDie: 5*3) + 100 (flat) = 115 - // FinalScore = (3 + 115) * 1 = 118 - Assert.AreEqual(118, result.FinalScore); - } + // FlatBonus = 15 (perDie: 5*3) + 100 (flat) = 115 + // FinalScore = (3 + 115) * 1 = 118 + Assert.AreEqual(118, result.FinalScore); + } - [Test] - public void Apply_ScopeFiltering_SkipsWrongScope() - { - var mod = ModifierData.CreateForTest("any", ModifierScope.AnyCategoryClosed, - ModifierEffectType.AddFlatToFinalScore, 50f); + [Test] + public void Apply_ScopeFiltering_SkipsWrongScope() + { + var mod = ModifierData.CreateForTest("any", ModifierScope.AnyCategoryClosed, + ModifierEffectType.AddFlatToFinalScore, 50f); - var modifiers = new List { mod }; - var result = ScoreResult.Create(10, new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance); + var modifiers = new List { mod }; + var result = ScoreResult.Create(10, new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance); - ModifierPipeline.Apply(modifiers, ref result, ModifierScope.SelectedCategory); + ModifierPipeline.Apply(modifiers, ref result, ModifierScope.SelectedCategory); - Assert.AreEqual(0, result.FlatBonus); - Assert.AreEqual(10, result.FinalScore); - } + Assert.AreEqual(0, result.FlatBonus); + Assert.AreEqual(10, result.FinalScore); + } - [Test] - public void Apply_CategoryFilter_SkipsWrongCategory() - { - var mod = ModifierData.CreateForTest("fh", ModifierScope.SelectedCategory, - ModifierEffectType.AddFlatToFinalScore, 15f, - targetCategory: YachtCategory.FullHouse, hasCategoryFilter: true); + [Test] + public void Apply_CategoryFilter_SkipsWrongCategory() + { + var mod = ModifierData.CreateForTest("fh", ModifierScope.SelectedCategory, + ModifierEffectType.AddFlatToFinalScore, 15f, + targetCategory: YachtCategory.FullHouse, hasCategoryFilter: true); - var modifiers = new List { mod }; - var result = ScoreResult.Create(5, new[] { 1, 1, 1, 1, 1 }, YachtCategory.Ones); + var modifiers = new List { mod }; + var result = ScoreResult.Create(5, new[] { 1, 1, 1, 1, 1 }, YachtCategory.Ones); - ModifierPipeline.Apply(modifiers, ref result, ModifierScope.SelectedCategory); + ModifierPipeline.Apply(modifiers, ref result, ModifierScope.SelectedCategory); - Assert.AreEqual(0, result.FlatBonus); - } + Assert.AreEqual(0, result.FlatBonus); + } - [Test] - public void Apply_CategoryFilter_AppliesMatchingCategory() - { - var mod = ModifierData.CreateForTest("fh", ModifierScope.SelectedCategory, - ModifierEffectType.AddFlatToFinalScore, 15f, - targetCategory: YachtCategory.FullHouse, hasCategoryFilter: true); + [Test] + public void Apply_CategoryFilter_AppliesMatchingCategory() + { + var mod = ModifierData.CreateForTest("fh", ModifierScope.SelectedCategory, + ModifierEffectType.AddFlatToFinalScore, 15f, + targetCategory: YachtCategory.FullHouse, hasCategoryFilter: true); - var modifiers = new List { mod }; - var result = ScoreResult.Create(25, new[] { 3, 3, 3, 2, 2 }, YachtCategory.FullHouse); + var modifiers = new List { mod }; + var result = ScoreResult.Create(25, new[] { 3, 3, 3, 2, 2 }, YachtCategory.FullHouse); - ModifierPipeline.Apply(modifiers, ref result, ModifierScope.SelectedCategory); + ModifierPipeline.Apply(modifiers, ref result, ModifierScope.SelectedCategory); - Assert.AreEqual(15, result.FlatBonus); - Assert.AreEqual(40, result.FinalScore); - } + Assert.AreEqual(15, result.FlatBonus); + Assert.AreEqual(40, result.FinalScore); + } - [Test] - public void Apply_MultipleModifiers_CorrectOrder() - { - var perDieAdd = ModifierData.CreateForTest("pda", ModifierScope.SelectedCategory, - ModifierEffectType.AddPerDieValue, 2f, dieValue: 3); - var perDieMul = ModifierData.CreateForTest("pdm", ModifierScope.SelectedCategory, - ModifierEffectType.MultiplyPerDieValue, 1.5f, dieValue: 3); - var flatAdd = ModifierData.CreateForTest("fa", ModifierScope.SelectedCategory, - ModifierEffectType.AddFlatToFinalScore, 10f); - var finalMul = ModifierData.CreateForTest("fm", ModifierScope.SelectedCategory, - ModifierEffectType.MultiplyFinalScore, 2f); + [Test] + public void Apply_MultipleModifiers_CorrectOrder() + { + var perDieAdd = ModifierData.CreateForTest("pda", ModifierScope.SelectedCategory, + ModifierEffectType.AddPerDieValue, 2f, dieValue: 3); + var perDieMul = ModifierData.CreateForTest("pdm", ModifierScope.SelectedCategory, + ModifierEffectType.MultiplyPerDieValue, 1.5f, dieValue: 3); + var flatAdd = ModifierData.CreateForTest("fa", ModifierScope.SelectedCategory, + ModifierEffectType.AddFlatToFinalScore, 10f); + var finalMul = ModifierData.CreateForTest("fm", ModifierScope.SelectedCategory, + ModifierEffectType.MultiplyFinalScore, 2f); - var modifiers = new List { finalMul, flatAdd, perDieMul, perDieAdd }; - // dice: [3, 3, 3, 1, 2] — 3 threes - var result = ScoreResult.Create(9, new[] { 3, 3, 3, 1, 2 }, YachtCategory.Threes); + var modifiers = new List { finalMul, flatAdd, perDieMul, perDieAdd }; + // dice: [3, 3, 3, 1, 2] — 3 threes + var result = ScoreResult.Create(9, new[] { 3, 3, 3, 1, 2 }, YachtCategory.Threes); - ModifierPipeline.Apply(modifiers, ref result, ModifierScope.SelectedCategory); + ModifierPipeline.Apply(modifiers, ref result, ModifierScope.SelectedCategory); - // Pass 1 (cat additive): perDieAdd: +2*3 = +6 FlatBonus - // Pass 2 (cat multiplicative): perDieMul: 1.5^3 = 3.375 Multiplier - // Pass 3 (final additive): flatAdd: +10 FlatBonus → total FlatBonus = 16 - // Pass 4 (final multiplicative): finalMul: 3.375 * 2 = 6.75 Multiplier - // FinalScore = floor((9 + 16) * 6.75) = floor(168.75) = 168 - Assert.AreEqual(6, result.FlatBonus + 10); // just check pipeline ran; full calc below - Assert.AreEqual(168, result.FinalScore); - } + // Pass 1 (cat additive): perDieAdd: +2*3 = +6 FlatBonus + // Pass 2 (cat multiplicative): perDieMul: 1.5^3 = 3.375 Multiplier + // Pass 3 (final additive): flatAdd: +10 FlatBonus → total FlatBonus = 16 + // Pass 4 (final multiplicative): finalMul: 3.375 * 2 = 6.75 Multiplier + // FinalScore = floor((9 + 16) * 6.75) = floor(168.75) = 168 + Assert.AreEqual(6, result.FlatBonus + 10); // just check pipeline ran; full calc below + Assert.AreEqual(168, result.FinalScore); + } - [Test] - public void Apply_NullModifiers_DoesNotThrow() - { - var result = ScoreResult.Create(10, new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance); + [Test] + public void Apply_NullModifiers_DoesNotThrow() + { + var result = ScoreResult.Create(10, new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance); - Assert.DoesNotThrow(() => - ModifierPipeline.Apply(null, ref result, ModifierScope.SelectedCategory)); + Assert.DoesNotThrow(() => + ModifierPipeline.Apply(null, ref result, ModifierScope.SelectedCategory)); - Assert.AreEqual(10, result.FinalScore); - } + Assert.AreEqual(10, result.FinalScore); + } - [Test] - public void Apply_EmptyList_NoChange() - { - var modifiers = new List(); - var result = ScoreResult.Create(10, new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance); + [Test] + public void Apply_EmptyList_NoChange() + { + var modifiers = new List(); + var result = ScoreResult.Create(10, new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance); - ModifierPipeline.Apply(modifiers, ref result, ModifierScope.SelectedCategory); + ModifierPipeline.Apply(modifiers, ref result, ModifierScope.SelectedCategory); - Assert.AreEqual(10, result.FinalScore); + Assert.AreEqual(10, result.FinalScore); + } } } -} diff --git a/Assets/Scripts/Tests/Editor/SaveSystemTests.cs b/Assets/Scripts/Tests/Editor/SaveSystemTests.cs index 136daf2..860e5f9 100644 --- a/Assets/Scripts/Tests/Editor/SaveSystemTests.cs +++ b/Assets/Scripts/Tests/Editor/SaveSystemTests.cs @@ -5,89 +5,88 @@ using YachtDice.Persistence; namespace YachtDice.Tests { - -public sealed class SaveSystemTests -{ - [SetUp] - public void SetUp() + public class SaveSystemTests { - SaveSystem.Delete(); - } - - [TearDown] - public void TearDown() - { - SaveSystem.Delete(); - } - - [Test] - public void SaveAndLoad_RoundTrip_PreservesData() - { - var data = new SaveData + [SetUp] + public void SetUp() { - Currency = 999, - OwnedModifiers = new List + SaveSystem.Delete(); + } + + [TearDown] + public void TearDown() + { + SaveSystem.Delete(); + } + + [Test] + public void SaveAndLoad_RoundTrip_PreservesData() + { + var data = new SaveData { - new() { ModifierId = "mod1", IsActive = true, RemainingUses = 3 }, - new() { ModifierId = "mod2", IsActive = false, RemainingUses = -1 } - } - }; + Currency = 999, + OwnedModifiers = new List + { + new() { ModifierId = "mod1", IsActive = true, RemainingUses = 3 }, + new() { ModifierId = "mod2", IsActive = false, RemainingUses = -1 } + } + }; - SaveSystem.Save(data); - var loaded = SaveSystem.Load(); + SaveSystem.Save(data); + var loaded = SaveSystem.Load(); - Assert.AreEqual(999, loaded.Currency); - Assert.AreEqual(2, loaded.OwnedModifiers.Count); - Assert.AreEqual("mod1", loaded.OwnedModifiers[0].ModifierId); - Assert.IsTrue(loaded.OwnedModifiers[0].IsActive); - Assert.AreEqual(3, loaded.OwnedModifiers[0].RemainingUses); - Assert.AreEqual("mod2", loaded.OwnedModifiers[1].ModifierId); - Assert.IsFalse(loaded.OwnedModifiers[1].IsActive); - } + Assert.AreEqual(999, loaded.Currency); + Assert.AreEqual(2, loaded.OwnedModifiers.Count); + Assert.AreEqual("mod1", loaded.OwnedModifiers[0].ModifierId); + Assert.IsTrue(loaded.OwnedModifiers[0].IsActive); + Assert.AreEqual(3, loaded.OwnedModifiers[0].RemainingUses); + Assert.AreEqual("mod2", loaded.OwnedModifiers[1].ModifierId); + Assert.IsFalse(loaded.OwnedModifiers[1].IsActive); + } - [Test] - public void Load_MissingKey_ReturnsDefault() - { - var loaded = SaveSystem.Load(); + [Test] + public void Load_MissingKey_ReturnsDefault() + { + var loaded = SaveSystem.Load(); - Assert.IsNotNull(loaded); - Assert.AreEqual(0, loaded.Currency); - Assert.AreEqual(0, loaded.OwnedModifiers.Count); - } + Assert.IsNotNull(loaded); + Assert.AreEqual(0, loaded.Currency); + Assert.AreEqual(0, loaded.OwnedModifiers.Count); + } - [Test] - public void HasSave_ReturnsFalseWhenEmpty() - { - Assert.IsFalse(SaveSystem.HasSave()); - } + [Test] + public void HasSave_ReturnsFalseWhenEmpty() + { + Assert.IsFalse(SaveSystem.HasSave()); + } - [Test] - public void HasSave_ReturnsTrueAfterSave() - { - SaveSystem.Save(new SaveData { Currency = 100 }); + [Test] + public void HasSave_ReturnsTrueAfterSave() + { + SaveSystem.Save(new SaveData { Currency = 100 }); - Assert.IsTrue(SaveSystem.HasSave()); - } + Assert.IsTrue(SaveSystem.HasSave()); + } - [Test] - public void Delete_RemovesSaveData() - { - SaveSystem.Save(new SaveData { Currency = 100 }); - SaveSystem.Delete(); + [Test] + public void Delete_RemovesSaveData() + { + SaveSystem.Save(new SaveData { Currency = 100 }); + SaveSystem.Delete(); - Assert.IsFalse(SaveSystem.HasSave()); - } + Assert.IsFalse(SaveSystem.HasSave()); + } - [Test] - public void Load_CorruptJson_ReturnsDefault() - { - PlayerPrefs.SetString("YachtDice_SaveData", "{invalid json!!!"); - PlayerPrefs.Save(); + [Test] + public void Load_CorruptJson_ReturnsDefault() + { + PlayerPrefs.SetString("YachtDice_SaveData", "{invalid json!!!"); + PlayerPrefs.Save(); - var loaded = SaveSystem.Load(); + var loaded = SaveSystem.Load(); - Assert.IsNotNull(loaded); - Assert.AreEqual(0, loaded.Currency); + Assert.IsNotNull(loaded); + Assert.AreEqual(0, loaded.Currency); + } } } -} diff --git a/Assets/Scripts/Tests/Editor/ScoringSystemTests.cs b/Assets/Scripts/Tests/Editor/ScoringSystemTests.cs index 6f94d54..e5e359e 100644 --- a/Assets/Scripts/Tests/Editor/ScoringSystemTests.cs +++ b/Assets/Scripts/Tests/Editor/ScoringSystemTests.cs @@ -6,99 +6,98 @@ using YachtDice.Modifiers; namespace YachtDice.Tests { - -public sealed class ScoringSystemTests -{ - private ScoringSystem CreateScoringSystem() + public class ScoringSystemTests { - var go = new GameObject("ScoringSystem"); - return go.AddComponent(); - } - - [TearDown] - public void TearDown() - { - foreach (var go in Object.FindObjectsByType(FindObjectsSortMode.None)) - Object.DestroyImmediate(go.gameObject); - } - - [Test] - public void PreviewScore_AppliesOnlySelectedCategoryModifiers() - { - var system = CreateScoringSystem(); - - var selectedMod = ModifierData.CreateForTest("sel", ModifierScope.SelectedCategory, - ModifierEffectType.AddFlatToFinalScore, 10f); - var anyCloseMod = ModifierData.CreateForTest("any", ModifierScope.AnyCategoryClosed, - ModifierEffectType.AddFlatToFinalScore, 100f); - - system.SetActiveModifiers(new List { selectedMod, anyCloseMod }); - - var result = system.PreviewScore(new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance); - - // Only SelectedCategory mod should apply in preview - Assert.AreEqual(10, result.FlatBonus); - Assert.AreEqual(25, result.FinalScore); // (15 + 10) * 1 - } - - [Test] - public void ScoreCategory_AppliesBothScopes() - { - var system = CreateScoringSystem(); - - var selectedMod = ModifierData.CreateForTest("sel", ModifierScope.SelectedCategory, - ModifierEffectType.AddFlatToFinalScore, 10f); - var anyCloseMod = ModifierData.CreateForTest("any", ModifierScope.AnyCategoryClosed, - ModifierEffectType.AddFlatToFinalScore, 20f); - - system.SetActiveModifiers(new List { selectedMod, anyCloseMod }); - - var result = system.ScoreCategory(new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance); - - // Both scopes should apply - Assert.AreEqual(30, result.FlatBonus); - Assert.AreEqual(45, result.FinalScore); // (15 + 30) * 1 - } - - [Test] - public void ScoreCategory_FiresOnCategoryConfirmed() - { - var system = CreateScoringSystem(); - YachtCategory firedCategory = (YachtCategory)(-1); - ScoreResult firedResult = default; - - system.OnCategoryConfirmed += (cat, res) => + private ScoringSystem CreateScoringSystem() { - firedCategory = cat; - firedResult = res; - }; + var go = new GameObject("ScoringSystem"); + return go.AddComponent(); + } - system.ScoreCategory(new[] { 1, 1, 1, 1, 1 }, YachtCategory.Ones); + [TearDown] + public void TearDown() + { + foreach (var go in Object.FindObjectsByType(FindObjectsSortMode.None)) + Object.DestroyImmediate(go.gameObject); + } - Assert.AreEqual(YachtCategory.Ones, firedCategory); - Assert.AreEqual(5, firedResult.BaseScore); - } + [Test] + public void PreviewScore_AppliesOnlySelectedCategoryModifiers() + { + var system = CreateScoringSystem(); - [Test] - public void ScoreCategory_PreventsDuplicateCategory() - { - var system = CreateScoringSystem(); - system.ScoreCategory(new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance); + var selectedMod = ModifierData.CreateForTest("sel", ModifierScope.SelectedCategory, + ModifierEffectType.AddFlatToFinalScore, 10f); + var anyCloseMod = ModifierData.CreateForTest("any", ModifierScope.AnyCategoryClosed, + ModifierEffectType.AddFlatToFinalScore, 100f); - Assert.Throws(() => - system.ScoreCategory(new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance)); - } + system.SetActiveModifiers(new List { selectedMod, anyCloseMod }); - [Test] - public void ScoreCategory_WithNoModifiers_CalculatesBaseOnly() - { - var system = CreateScoringSystem(); - var result = system.ScoreCategory(new[] { 6, 6, 6, 6, 6 }, YachtCategory.Yacht); + var result = system.PreviewScore(new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance); - Assert.AreEqual(50, result.BaseScore); - Assert.AreEqual(0, result.FlatBonus); - Assert.AreEqual(1f, result.Multiplier); - Assert.AreEqual(50, result.FinalScore); + // Only SelectedCategory mod should apply in preview + Assert.AreEqual(10, result.FlatBonus); + Assert.AreEqual(25, result.FinalScore); // (15 + 10) * 1 + } + + [Test] + public void ScoreCategory_AppliesBothScopes() + { + var system = CreateScoringSystem(); + + var selectedMod = ModifierData.CreateForTest("sel", ModifierScope.SelectedCategory, + ModifierEffectType.AddFlatToFinalScore, 10f); + var anyCloseMod = ModifierData.CreateForTest("any", ModifierScope.AnyCategoryClosed, + ModifierEffectType.AddFlatToFinalScore, 20f); + + system.SetActiveModifiers(new List { selectedMod, anyCloseMod }); + + var result = system.ScoreCategory(new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance); + + // Both scopes should apply + Assert.AreEqual(30, result.FlatBonus); + Assert.AreEqual(45, result.FinalScore); // (15 + 30) * 1 + } + + [Test] + public void ScoreCategory_FiresOnCategoryConfirmed() + { + var system = CreateScoringSystem(); + YachtCategory firedCategory = (YachtCategory)(-1); + ScoreResult firedResult = default; + + system.OnCategoryConfirmed += (cat, res) => + { + firedCategory = cat; + firedResult = res; + }; + + system.ScoreCategory(new[] { 1, 1, 1, 1, 1 }, YachtCategory.Ones); + + Assert.AreEqual(YachtCategory.Ones, firedCategory); + Assert.AreEqual(5, firedResult.BaseScore); + } + + [Test] + public void ScoreCategory_PreventsDuplicateCategory() + { + var system = CreateScoringSystem(); + system.ScoreCategory(new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance); + + Assert.Throws(() => + system.ScoreCategory(new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance)); + } + + [Test] + public void ScoreCategory_WithNoModifiers_CalculatesBaseOnly() + { + var system = CreateScoringSystem(); + var result = system.ScoreCategory(new[] { 6, 6, 6, 6, 6 }, YachtCategory.Yacht); + + Assert.AreEqual(50, result.BaseScore); + Assert.AreEqual(0, result.FlatBonus); + Assert.AreEqual(1f, result.Multiplier); + Assert.AreEqual(50, result.FinalScore); + } } } -} diff --git a/Assets/Scripts/Tests/Editor/ShopModelTests.cs b/Assets/Scripts/Tests/Editor/ShopModelTests.cs index cf920d6..deeb7e7 100644 --- a/Assets/Scripts/Tests/Editor/ShopModelTests.cs +++ b/Assets/Scripts/Tests/Editor/ShopModelTests.cs @@ -7,132 +7,131 @@ using YachtDice.Modifiers; namespace YachtDice.Tests { - -public sealed class ShopModelTests -{ - private CurrencyBank bank; - private InventoryModel inventory; - private ShopModel shop; - - [SetUp] - public void SetUp() + public sealed class ShopModelTests { - var go = new GameObject("Bank"); - bank = go.AddComponent(); - bank.SetBalance(500); + private CurrencyBank bank; + private InventoryModel inventory; + private ShopModel shop; - inventory = new InventoryModel(5); - shop = new ShopModel(bank, inventory); - } + [SetUp] + public void SetUp() + { + var go = new GameObject("Bank"); + bank = go.AddComponent(); + bank.SetBalance(500); - [TearDown] - public void TearDown() - { - foreach (var go in Object.FindObjectsByType(FindObjectsSortMode.None)) - Object.DestroyImmediate(go.gameObject); - } + inventory = new InventoryModel(5); + shop = new ShopModel(bank, inventory); + } - [Test] - public void TryPurchase_SucceedsWithSufficientCurrency() - { - var mod = ModifierData.CreateForTest("test", ModifierScope.SelectedCategory, - ModifierEffectType.AddFlatToFinalScore, 10f, shopPrice: 100); + [TearDown] + public void TearDown() + { + foreach (var go in Object.FindObjectsByType(FindObjectsSortMode.None)) + Object.DestroyImmediate(go.gameObject); + } - bool result = shop.TryPurchase(mod); + [Test] + public void TryPurchase_SucceedsWithSufficientCurrency() + { + var mod = ModifierData.CreateForTest("test", ModifierScope.SelectedCategory, + ModifierEffectType.AddFlatToFinalScore, 10f, shopPrice: 100); - Assert.IsTrue(result); - Assert.AreEqual(400, bank.Balance); - Assert.AreEqual(1, inventory.OwnedModifiers.Count); - } + bool result = shop.TryPurchase(mod); - [Test] - public void TryPurchase_FailsWhenBroke() - { - bank.SetBalance(10); - var mod = ModifierData.CreateForTest("test", ModifierScope.SelectedCategory, - ModifierEffectType.AddFlatToFinalScore, 10f, shopPrice: 100); + Assert.IsTrue(result); + Assert.AreEqual(400, bank.Balance); + Assert.AreEqual(1, inventory.OwnedModifiers.Count); + } - bool result = shop.TryPurchase(mod); + [Test] + public void TryPurchase_FailsWhenBroke() + { + bank.SetBalance(10); + var mod = ModifierData.CreateForTest("test", ModifierScope.SelectedCategory, + ModifierEffectType.AddFlatToFinalScore, 10f, shopPrice: 100); - Assert.IsFalse(result); - Assert.AreEqual(10, bank.Balance); - Assert.AreEqual(0, inventory.OwnedModifiers.Count); - } + bool result = shop.TryPurchase(mod); - [Test] - public void TryPurchase_PermanentCannotBeBoughtTwice() - { - var mod = ModifierData.CreateForTest("perm", ModifierScope.SelectedCategory, - ModifierEffectType.AddFlatToFinalScore, 10f, - durability: ModifierDurability.Permanent, shopPrice: 100); + Assert.IsFalse(result); + Assert.AreEqual(10, bank.Balance); + Assert.AreEqual(0, inventory.OwnedModifiers.Count); + } - shop.TryPurchase(mod); - bool secondResult = shop.TryPurchase(mod); + [Test] + public void TryPurchase_PermanentCannotBeBoughtTwice() + { + var mod = ModifierData.CreateForTest("perm", ModifierScope.SelectedCategory, + ModifierEffectType.AddFlatToFinalScore, 10f, + durability: ModifierDurability.Permanent, shopPrice: 100); - Assert.IsFalse(secondResult); - Assert.AreEqual(400, bank.Balance); - Assert.AreEqual(1, inventory.OwnedModifiers.Count); - } + shop.TryPurchase(mod); + bool secondResult = shop.TryPurchase(mod); - [Test] - public void TryPurchase_LimitedCanBeReBought() - { - var mod = ModifierData.CreateForTest("limited", ModifierScope.SelectedCategory, - ModifierEffectType.AddFlatToFinalScore, 10f, - durability: ModifierDurability.LimitedUses, maxUses: 3, shopPrice: 100); + Assert.IsFalse(secondResult); + Assert.AreEqual(400, bank.Balance); + Assert.AreEqual(1, inventory.OwnedModifiers.Count); + } - shop.TryPurchase(mod); - bool secondResult = shop.TryPurchase(mod); + [Test] + public void TryPurchase_LimitedCanBeReBought() + { + var mod = ModifierData.CreateForTest("limited", ModifierScope.SelectedCategory, + ModifierEffectType.AddFlatToFinalScore, 10f, + durability: ModifierDurability.LimitedUses, maxUses: 3, shopPrice: 100); - Assert.IsTrue(secondResult); - Assert.AreEqual(300, bank.Balance); - Assert.AreEqual(2, inventory.OwnedModifiers.Count); - } + shop.TryPurchase(mod); + bool secondResult = shop.TryPurchase(mod); - [Test] - public void TryPurchase_FiresPurchaseEvent() - { - ModifierData purchased = null; - shop.OnItemPurchased += data => purchased = data; + Assert.IsTrue(secondResult); + Assert.AreEqual(300, bank.Balance); + Assert.AreEqual(2, inventory.OwnedModifiers.Count); + } - var mod = ModifierData.CreateForTest("test", ModifierScope.SelectedCategory, - ModifierEffectType.AddFlatToFinalScore, 10f, shopPrice: 100); + [Test] + public void TryPurchase_FiresPurchaseEvent() + { + ModifierData purchased = null; + shop.OnItemPurchased += data => purchased = data; - shop.TryPurchase(mod); + var mod = ModifierData.CreateForTest("test", ModifierScope.SelectedCategory, + ModifierEffectType.AddFlatToFinalScore, 10f, shopPrice: 100); - Assert.IsNotNull(purchased); - Assert.AreEqual("test", purchased.Id); - } + shop.TryPurchase(mod); - [Test] - public void GetItemState_Available_WhenCanAfford() - { - var mod = ModifierData.CreateForTest("test", ModifierScope.SelectedCategory, - ModifierEffectType.AddFlatToFinalScore, 10f, shopPrice: 100); + Assert.IsNotNull(purchased); + Assert.AreEqual("test", purchased.Id); + } - Assert.AreEqual(ShopItemState.Available, shop.GetItemState(mod)); - } + [Test] + public void GetItemState_Available_WhenCanAfford() + { + var mod = ModifierData.CreateForTest("test", ModifierScope.SelectedCategory, + ModifierEffectType.AddFlatToFinalScore, 10f, shopPrice: 100); - [Test] - public void GetItemState_TooExpensive_WhenCannotAfford() - { - bank.SetBalance(10); - var mod = ModifierData.CreateForTest("test", ModifierScope.SelectedCategory, - ModifierEffectType.AddFlatToFinalScore, 10f, shopPrice: 100); + Assert.AreEqual(ShopItemState.Available, shop.GetItemState(mod)); + } - Assert.AreEqual(ShopItemState.TooExpensive, shop.GetItemState(mod)); - } + [Test] + public void GetItemState_TooExpensive_WhenCannotAfford() + { + bank.SetBalance(10); + var mod = ModifierData.CreateForTest("test", ModifierScope.SelectedCategory, + ModifierEffectType.AddFlatToFinalScore, 10f, shopPrice: 100); - [Test] - public void GetItemState_Owned_WhenPermanentPurchased() - { - var mod = ModifierData.CreateForTest("perm", ModifierScope.SelectedCategory, - ModifierEffectType.AddFlatToFinalScore, 10f, - durability: ModifierDurability.Permanent, shopPrice: 100); + Assert.AreEqual(ShopItemState.TooExpensive, shop.GetItemState(mod)); + } - shop.TryPurchase(mod); + [Test] + public void GetItemState_Owned_WhenPermanentPurchased() + { + var mod = ModifierData.CreateForTest("perm", ModifierScope.SelectedCategory, + ModifierEffectType.AddFlatToFinalScore, 10f, + durability: ModifierDurability.Permanent, shopPrice: 100); - Assert.AreEqual(ShopItemState.Owned, shop.GetItemState(mod)); + shop.TryPurchase(mod); + + Assert.AreEqual(ShopItemState.Owned, shop.GetItemState(mod)); + } } } -} diff --git a/Assets/Scripts/UI/CategoryRowView.cs b/Assets/Scripts/UI/CategoryRowView.cs index 98ed4e1..f2de289 100644 --- a/Assets/Scripts/UI/CategoryRowView.cs +++ b/Assets/Scripts/UI/CategoryRowView.cs @@ -6,89 +6,88 @@ using YachtDice.Scoring; namespace YachtDice.UI { - -public sealed class CategoryRowView : MonoBehaviour -{ - [Header("UI Elements")] - [SerializeField] private TMP_Text categoryNameText; - [SerializeField] private TMP_Text previewText; - [SerializeField] private TMP_Text recordedScoreText; - [SerializeField] private Button selectButton; - [SerializeField] private Image background; - - [Header("Colors")] - [SerializeField] private Color normalColor = new Color(0.95f, 0.95f, 0.95f, 1f); - [SerializeField] private Color usedColor = new Color(0.75f, 0.75f, 0.75f, 1f); - [SerializeField] private Color previewPositiveColor = new Color(0.85f, 1f, 0.85f, 1f); - [SerializeField] private Color previewZeroColor = new Color(1f, 0.88f, 0.88f, 1f); - - private YachtCategory category; - private bool isUsed; - - public event Action OnCategorySelected; - - public void Initialize(YachtCategory cat, string displayName) + public class CategoryRowView : MonoBehaviour { - category = cat; - isUsed = false; - categoryNameText.text = displayName; - previewText.text = ""; - recordedScoreText.text = "-"; - selectButton.onClick.AddListener(HandleClick); - SetInteractable(false); - background.color = normalColor; - } + [Header("UI Elements")] + [SerializeField] private TMP_Text categoryNameText; + [SerializeField] private TMP_Text previewText; + [SerializeField] private TMP_Text recordedScoreText; + [SerializeField] private Button selectButton; + [SerializeField] private Image background; - public void ShowPreview(int previewScore) - { - if (isUsed) return; - previewText.text = previewScore.ToString(); - background.color = previewScore > 0 ? previewPositiveColor : previewZeroColor; - } + [Header("Colors")] + [SerializeField] private Color normalColor = new Color(0.95f, 0.95f, 0.95f, 1f); + [SerializeField] private Color usedColor = new Color(0.75f, 0.75f, 0.75f, 1f); + [SerializeField] private Color previewPositiveColor = new Color(0.85f, 1f, 0.85f, 1f); + [SerializeField] private Color previewZeroColor = new Color(1f, 0.88f, 0.88f, 1f); - public void HidePreview() - { - if (isUsed) return; - previewText.text = ""; - background.color = normalColor; - } + private YachtCategory category; + private bool isUsed; - public void SetRecordedScore(int score) - { - isUsed = true; - recordedScoreText.text = score.ToString(); - previewText.text = ""; - SetInteractable(false); - background.color = usedColor; - } + public event Action OnCategorySelected; - public void SetInteractable(bool interactable) - { - if (isUsed) + public void Initialize(YachtCategory cat, string displayName) { - selectButton.interactable = false; - return; + category = cat; + isUsed = false; + categoryNameText.text = displayName; + previewText.text = ""; + recordedScoreText.text = "-"; + selectButton.onClick.AddListener(HandleClick); + SetInteractable(false); + background.color = normalColor; } - selectButton.interactable = interactable; - } - public void ResetRow() - { - isUsed = false; - previewText.text = ""; - recordedScoreText.text = "-"; - SetInteractable(false); - background.color = normalColor; - } + public void ShowPreview(int previewScore) + { + if (isUsed) return; + previewText.text = previewScore.ToString(); + background.color = previewScore > 0 ? previewPositiveColor : previewZeroColor; + } - private void HandleClick() - { - OnCategorySelected?.Invoke(category); - } + public void HidePreview() + { + if (isUsed) return; + previewText.text = ""; + background.color = normalColor; + } - private void OnDestroy() - { - selectButton.onClick.RemoveListener(HandleClick); + public void SetRecordedScore(int score) + { + isUsed = true; + recordedScoreText.text = score.ToString(); + previewText.text = ""; + SetInteractable(false); + background.color = usedColor; + } + + public void SetInteractable(bool interactable) + { + if (isUsed) + { + selectButton.interactable = false; + return; + } + selectButton.interactable = interactable; + } + + public void ResetRow() + { + isUsed = false; + previewText.text = ""; + recordedScoreText.text = "-"; + SetInteractable(false); + background.color = normalColor; + } + + private void HandleClick() + { + OnCategorySelected?.Invoke(category); + } + + private void OnDestroy() + { + selectButton.onClick.RemoveListener(HandleClick); + } } } -} diff --git a/Assets/Scripts/UI/DicePanelView.cs b/Assets/Scripts/UI/DicePanelView.cs index 1997123..27cea16 100644 --- a/Assets/Scripts/UI/DicePanelView.cs +++ b/Assets/Scripts/UI/DicePanelView.cs @@ -5,101 +5,100 @@ using TMPro; namespace YachtDice.UI { - -public sealed class DicePanelView : MonoBehaviour -{ - [Header("Dice Display")] - [SerializeField] private Button[] diceButtons = new Button[5]; - [SerializeField] private TMP_Text[] diceValueTexts = new TMP_Text[5]; - [SerializeField] private Image[] diceBackgrounds = new Image[5]; - - [Header("Roll")] - [SerializeField] private Button rollButton; - [SerializeField] private TMP_Text rollButtonText; - - [Header("Colors")] - [SerializeField] private Color unlockedColor = Color.white; - [SerializeField] private Color lockedColor = new Color(1f, 0.85f, 0.4f, 1f); - - public event Action OnDiceToggled; - public event Action OnRollClicked; - - private void Awake() + public class DicePanelView : MonoBehaviour { - for (int i = 0; i < diceButtons.Length; i++) + [Header("Dice Display")] + [SerializeField] private Button[] diceButtons = new Button[5]; + [SerializeField] private TMP_Text[] diceValueTexts = new TMP_Text[5]; + [SerializeField] private Image[] diceBackgrounds = new Image[5]; + + [Header("Roll")] + [SerializeField] private Button rollButton; + [SerializeField] private TMP_Text rollButtonText; + + [Header("Colors")] + [SerializeField] private Color unlockedColor = Color.white; + [SerializeField] private Color lockedColor = new Color(1f, 0.85f, 0.4f, 1f); + + public event Action OnDiceToggled; + public event Action OnRollClicked; + + private void Awake() { - int capturedIndex = i; - diceButtons[i].onClick.AddListener(() => OnDiceToggled?.Invoke(capturedIndex)); + for (int i = 0; i < diceButtons.Length; i++) + { + int capturedIndex = i; + diceButtons[i].onClick.AddListener(() => OnDiceToggled?.Invoke(capturedIndex)); + } + + rollButton.onClick.AddListener(() => OnRollClicked?.Invoke()); + + for (int i = 0; i < diceValueTexts.Length; i++) + { + diceValueTexts[i].text = "?"; + diceBackgrounds[i].color = unlockedColor; + diceButtons[i].interactable = false; + } + + SetRollButtonState(true, 0, 3); } - rollButton.onClick.AddListener(() => OnRollClicked?.Invoke()); - - for (int i = 0; i < diceValueTexts.Length; i++) + public void SetDieValue(int index, int value) { - diceValueTexts[i].text = "?"; - diceBackgrounds[i].color = unlockedColor; - diceButtons[i].interactable = false; + if (index >= 0 && index < diceValueTexts.Length) + diceValueTexts[index].text = value.ToString(); } - SetRollButtonState(true, 0, 3); - } - - public void SetDieValue(int index, int value) - { - if (index >= 0 && index < diceValueTexts.Length) - diceValueTexts[index].text = value.ToString(); - } - - public void SetAllDiceValues(int[] values) - { - for (int i = 0; i < values.Length && i < diceValueTexts.Length; i++) - diceValueTexts[i].text = values[i].ToString(); - } - - public void SetDieLocked(int index, bool isLocked) - { - if (index >= 0 && index < diceBackgrounds.Length) - diceBackgrounds[index].color = isLocked ? lockedColor : unlockedColor; - } - - public void SetDiceInteractable(bool interactable) - { - for (int i = 0; i < diceButtons.Length; i++) - diceButtons[i].interactable = interactable; - } - - public void SetRollButtonState(bool interactable, int currentRoll, int maxRolls) - { - rollButton.interactable = interactable; - - if (currentRoll >= maxRolls) - rollButtonText.text = "Выберите категорию"; - else - rollButtonText.text = $"Бросок {currentRoll + 1}/{maxRolls}"; - } - - public void ResetForNewTurn() - { - for (int i = 0; i < diceValueTexts.Length; i++) + public void SetAllDiceValues(int[] values) { - diceValueTexts[i].text = "?"; - diceBackgrounds[i].color = unlockedColor; - diceButtons[i].interactable = false; + for (int i = 0; i < values.Length && i < diceValueTexts.Length; i++) + diceValueTexts[i].text = values[i].ToString(); } - } - public void ResetForNewGame() - { - ResetForNewTurn(); - SetRollButtonState(true, 0, 3); - } + public void SetDieLocked(int index, bool isLocked) + { + if (index >= 0 && index < diceBackgrounds.Length) + diceBackgrounds[index].color = isLocked ? lockedColor : unlockedColor; + } - private void OnDestroy() - { - for (int i = 0; i < diceButtons.Length; i++) - diceButtons[i].onClick.RemoveAllListeners(); + public void SetDiceInteractable(bool interactable) + { + for (int i = 0; i < diceButtons.Length; i++) + diceButtons[i].interactable = interactable; + } - rollButton.onClick.RemoveAllListeners(); + public void SetRollButtonState(bool interactable, int currentRoll, int maxRolls) + { + rollButton.interactable = interactable; + + if (currentRoll >= maxRolls) + rollButtonText.text = "Выберите категорию"; + else + rollButtonText.text = $"Бросок {currentRoll + 1}/{maxRolls}"; + } + + public void ResetForNewTurn() + { + for (int i = 0; i < diceValueTexts.Length; i++) + { + diceValueTexts[i].text = "?"; + diceBackgrounds[i].color = unlockedColor; + diceButtons[i].interactable = false; + } + } + + public void ResetForNewGame() + { + ResetForNewTurn(); + SetRollButtonState(true, 0, 3); + } + + private void OnDestroy() + { + for (int i = 0; i < diceButtons.Length; i++) + diceButtons[i].onClick.RemoveAllListeners(); + + rollButton.onClick.RemoveAllListeners(); + } } } -} diff --git a/Assets/Scripts/UI/GameController.cs b/Assets/Scripts/UI/GameController.cs index fcbf1c3..fe040ec 100644 --- a/Assets/Scripts/UI/GameController.cs +++ b/Assets/Scripts/UI/GameController.cs @@ -11,348 +11,347 @@ using YachtDice.Modifiers; namespace YachtDice.UI { - -public sealed class GameController : MonoBehaviour -{ - [Header("Model")] - [SerializeField] private GameManager gameManager; - [SerializeField] private ScoringSystem scoringSystem; - [SerializeField] private DiceManager diceManager; - - [Header("Views")] - [SerializeField] private ScoreCardView scoreCardView; - [SerializeField] private DicePanelView dicePanelView; - [SerializeField] private GameInfoView gameInfoView; - - [Header("Economy & Modifiers")] - [SerializeField] private CurrencyBank currencyBank; - [SerializeField] private ShopController shopController; - [SerializeField] private InventoryController inventoryController; - - [Header("Settings")] - [SerializeField] private int maxRollsPerTurn = 3; - [SerializeField] private int maxActiveModifierSlots = 5; - - private static readonly YachtCategory[] UpperCategories = + public class GameController : MonoBehaviour { - YachtCategory.Ones, YachtCategory.Twos, YachtCategory.Threes, - YachtCategory.Fours, YachtCategory.Fives, YachtCategory.Sixes - }; + [Header("Model")] + [SerializeField] private GameManager gameManager; + [SerializeField] private ScoringSystem scoringSystem; + [SerializeField] private DiceManager diceManager; - private const int UpperBonusThreshold = 63; - private const int UpperBonusValue = 35; + [Header("Views")] + [SerializeField] private ScoreCardView scoreCardView; + [SerializeField] private DicePanelView dicePanelView; + [SerializeField] private GameInfoView gameInfoView; - private int totalCategoryCount; + [Header("Economy & Modifiers")] + [SerializeField] private CurrencyBank currencyBank; + [SerializeField] private ShopController shopController; + [SerializeField] private InventoryController inventoryController; - private InventoryModel inventoryModel; - private ShopModel shopModel; + [Header("Settings")] + [SerializeField] private int maxRollsPerTurn = 3; + [SerializeField] private int maxActiveModifierSlots = 5; - // ── Lifecycle ────────────────────────────────────────────── - - private void Awake() - { - totalCategoryCount = Enum.GetValues(typeof(YachtCategory)).Length; - - // Model → Controller - gameManager.OnTurnStarted += HandleTurnStarted; - gameManager.OnRollComplete += HandleRollComplete; - gameManager.OnScored += HandleScored; - gameManager.OnGameOver += HandleGameOver; - diceManager.OnDieSettled += HandleDieSettled; - - // View → Controller - scoreCardView.OnCategorySelected += HandleCategorySelected; - dicePanelView.OnRollClicked += HandleRollClicked; - dicePanelView.OnDiceToggled += HandleDiceToggled; - gameInfoView.OnNewGameClicked += HandleNewGameClicked; - gameInfoView.OnShopClicked += HandleShopClicked; - gameInfoView.OnInventoryClicked += HandleInventoryClicked; - - // Currency - if (currencyBank != null) - currencyBank.OnBalanceChanged += HandleCurrencyChanged; - - InitializeModifierSystems(); - } - - private void OnDestroy() - { - gameManager.OnTurnStarted -= HandleTurnStarted; - gameManager.OnRollComplete -= HandleRollComplete; - gameManager.OnScored -= HandleScored; - gameManager.OnGameOver -= HandleGameOver; - diceManager.OnDieSettled -= HandleDieSettled; - - scoreCardView.OnCategorySelected -= HandleCategorySelected; - dicePanelView.OnRollClicked -= HandleRollClicked; - dicePanelView.OnDiceToggled -= HandleDiceToggled; - gameInfoView.OnNewGameClicked -= HandleNewGameClicked; - gameInfoView.OnShopClicked -= HandleShopClicked; - gameInfoView.OnInventoryClicked -= HandleInventoryClicked; - - if (currencyBank != null) - currencyBank.OnBalanceChanged -= HandleCurrencyChanged; - - if (inventoryModel != null) - inventoryModel.OnInventoryChanged -= HandleInventoryChangedForSave; - } - - // ── Modifier System Init ───────────────────────────────── - - private void InitializeModifierSystems() - { - inventoryModel = new InventoryModel(maxActiveModifierSlots); - inventoryModel.OnInventoryChanged += HandleInventoryChangedForSave; - - ShopCatalog catalog = shopController != null ? shopController.Catalog : null; - - shopModel = new ShopModel(currencyBank, inventoryModel); - - LoadSaveData(catalog); - - if (inventoryController != null) - inventoryController.Initialize(inventoryModel); - - if (shopController != null) - shopController.Initialize(shopModel); - - if (currencyBank != null) - gameInfoView.SetCurrencyText(currencyBank.Balance); - } - - private void LoadSaveData(ShopCatalog catalog) - { - SaveData save = SaveSystem.Load(); - - if (currencyBank != null && save.Currency > 0) - currencyBank.SetBalance(save.Currency); - - if (catalog != null && save.OwnedModifiers.Count > 0) + private static readonly YachtCategory[] UpperCategories = { - var runtimeList = new List(); - var permanentIds = new HashSet(); - - for (int i = 0; i < save.OwnedModifiers.Count; i++) - { - var entry = save.OwnedModifiers[i]; - ModifierData data = catalog.FindById(entry.ModifierId); - - if (data == null) - { - Debug.LogWarning($"Modifier '{entry.ModifierId}' not found in catalog, skipping."); - continue; - } - - var runtime = new ModifierRuntime - { - ModifierId = entry.ModifierId, - IsActive = entry.IsActive, - RemainingUses = entry.RemainingUses, - Data = data - }; - - runtimeList.Add(runtime); - - if (data.Durability == ModifierDurability.Permanent) - permanentIds.Add(data.Id); - } - - inventoryModel.LoadState(runtimeList); - shopModel.LoadPurchasedPermanentIds(permanentIds); - } - } - - private void PerformSave() - { - var save = new SaveData - { - Currency = currencyBank != null ? currencyBank.Balance : 0 + YachtCategory.Ones, YachtCategory.Twos, YachtCategory.Threes, + YachtCategory.Fours, YachtCategory.Fives, YachtCategory.Sixes }; - var owned = inventoryModel.GetAllForSave(); - for (int i = 0; i < owned.Count; i++) + private const int UpperBonusThreshold = 63; + private const int UpperBonusValue = 35; + + private int totalCategoryCount; + + private InventoryModel inventoryModel; + private ShopModel shopModel; + + // ── Lifecycle ────────────────────────────────────────────── + + private void Awake() { - save.OwnedModifiers.Add(new ModifierSaveEntry - { - ModifierId = owned[i].ModifierId, - IsActive = owned[i].IsActive, - RemainingUses = owned[i].RemainingUses - }); + totalCategoryCount = Enum.GetValues(typeof(YachtCategory)).Length; + + // Model → Controller + gameManager.OnTurnStarted += HandleTurnStarted; + gameManager.OnRollComplete += HandleRollComplete; + gameManager.OnScored += HandleScored; + gameManager.OnGameOver += HandleGameOver; + diceManager.OnDieSettled += HandleDieSettled; + + // View → Controller + scoreCardView.OnCategorySelected += HandleCategorySelected; + dicePanelView.OnRollClicked += HandleRollClicked; + dicePanelView.OnDiceToggled += HandleDiceToggled; + gameInfoView.OnNewGameClicked += HandleNewGameClicked; + gameInfoView.OnShopClicked += HandleShopClicked; + gameInfoView.OnInventoryClicked += HandleInventoryClicked; + + // Currency + if (currencyBank != null) + currencyBank.OnBalanceChanged += HandleCurrencyChanged; + + InitializeModifierSystems(); } - SaveSystem.Save(save); - } - - // ── Model Event Handlers ────────────────────────────────── - - private void HandleTurnStarted(int turn) - { - gameInfoView.SetTurnText(turn, totalCategoryCount); - dicePanelView.ResetForNewTurn(); - dicePanelView.SetRollButtonState(true, 0, maxRollsPerTurn); - scoreCardView.ClearAllPreviews(); - } - - private void HandleRollComplete(int rollNumber) - { - bool canRollAgain = gameManager.CanRoll; - dicePanelView.SetRollButtonState(canRollAgain, rollNumber, maxRollsPerTurn); - dicePanelView.SetDiceInteractable(true); - - int[] values = diceManager.GetCurrentValues(); - dicePanelView.SetAllDiceValues(values); - - UpdatePreviewScores(values); - } - - private void HandleDieSettled(int index, int value) - { - dicePanelView.SetDieValue(index, value); - } - - private void HandleScored(YachtCategory category, int finalScore) - { - scoreCardView.SetCategoryScored(category, finalScore); - UpdateTotalDisplay(); - PerformSave(); - } - - private void HandleGameOver(int totalScore) - { - dicePanelView.SetRollButtonState(false, maxRollsPerTurn, maxRollsPerTurn); - dicePanelView.SetDiceInteractable(false); - scoreCardView.SetAllInteractable(false); - - int displayTotal = CalculateDisplayTotal(); - gameInfoView.ShowGameOver(displayTotal); - PerformSave(); - } - - // ── View Event Handlers ─────────────────────────────────── - - private void HandleRollClicked() - { - if (!gameManager.CanRoll) return; - - dicePanelView.SetRollButtonState(false, gameManager.CurrentRoll, maxRollsPerTurn); - dicePanelView.SetDiceInteractable(false); - scoreCardView.SetAllInteractable(false); - - gameManager.Roll(); - } - - private void HandleDiceToggled(int index) - { - if (gameManager.CurrentRoll == 0) return; - if (diceManager.IsAnyRolling) return; - - gameManager.ToggleDiceLock(index); - - bool isLocked = diceManager.IsLocked(index); - dicePanelView.SetDieLocked(index, isLocked); - } - - private void HandleCategorySelected(YachtCategory category) - { - if (!gameManager.CanScore) return; - if (scoringSystem.IsCategoryUsed(category)) return; - - gameManager.ScoreInCategory(category); - } - - private void HandleNewGameClicked() - { - gameInfoView.HideGameOver(); - scoreCardView.ResetAll(); - dicePanelView.ResetForNewGame(); - gameManager.StartNewGame(); - } - - private void HandleShopClicked() - { - if (shopController != null) + private void OnDestroy() { - var shopView = shopController.GetComponentInChildren(true); - if (shopView != null) + gameManager.OnTurnStarted -= HandleTurnStarted; + gameManager.OnRollComplete -= HandleRollComplete; + gameManager.OnScored -= HandleScored; + gameManager.OnGameOver -= HandleGameOver; + diceManager.OnDieSettled -= HandleDieSettled; + + scoreCardView.OnCategorySelected -= HandleCategorySelected; + dicePanelView.OnRollClicked -= HandleRollClicked; + dicePanelView.OnDiceToggled -= HandleDiceToggled; + gameInfoView.OnNewGameClicked -= HandleNewGameClicked; + gameInfoView.OnShopClicked -= HandleShopClicked; + gameInfoView.OnInventoryClicked -= HandleInventoryClicked; + + if (currencyBank != null) + currencyBank.OnBalanceChanged -= HandleCurrencyChanged; + + if (inventoryModel != null) + inventoryModel.OnInventoryChanged -= HandleInventoryChangedForSave; + } + + // ── Modifier System Init ───────────────────────────────── + + private void InitializeModifierSystems() + { + inventoryModel = new InventoryModel(maxActiveModifierSlots); + inventoryModel.OnInventoryChanged += HandleInventoryChangedForSave; + + ShopCatalog catalog = shopController != null ? shopController.Catalog : null; + + shopModel = new ShopModel(currencyBank, inventoryModel); + + LoadSaveData(catalog); + + if (inventoryController != null) + inventoryController.Initialize(inventoryModel); + + if (shopController != null) + shopController.Initialize(shopModel); + + if (currencyBank != null) + gameInfoView.SetCurrencyText(currencyBank.Balance); + } + + private void LoadSaveData(ShopCatalog catalog) + { + SaveData save = SaveSystem.Load(); + + if (currencyBank != null && save.Currency > 0) + currencyBank.SetBalance(save.Currency); + + if (catalog != null && save.OwnedModifiers.Count > 0) { - if (shopView.IsVisible) shopView.Hide(); - else shopView.Show(); + var runtimeList = new List(); + var permanentIds = new HashSet(); + + for (int i = 0; i < save.OwnedModifiers.Count; i++) + { + var entry = save.OwnedModifiers[i]; + ModifierData data = catalog.FindById(entry.ModifierId); + + if (data == null) + { + Debug.LogWarning($"Modifier '{entry.ModifierId}' not found in catalog, skipping."); + continue; + } + + var runtime = new ModifierRuntime + { + ModifierId = entry.ModifierId, + IsActive = entry.IsActive, + RemainingUses = entry.RemainingUses, + Data = data + }; + + runtimeList.Add(runtime); + + if (data.Durability == ModifierDurability.Permanent) + permanentIds.Add(data.Id); + } + + inventoryModel.LoadState(runtimeList); + shopModel.LoadPurchasedPermanentIds(permanentIds); } } - } - private void HandleInventoryClicked() - { - if (inventoryController != null) + private void PerformSave() { - var inventoryView = inventoryController.GetComponentInChildren(true); - if (inventoryView != null) + var save = new SaveData { - if (inventoryView.IsVisible) inventoryView.Hide(); - else inventoryView.Show(); + Currency = currencyBank != null ? currencyBank.Balance : 0 + }; + + var owned = inventoryModel.GetAllForSave(); + for (int i = 0; i < owned.Count; i++) + { + save.OwnedModifiers.Add(new ModifierSaveEntry + { + ModifierId = owned[i].ModifierId, + IsActive = owned[i].IsActive, + RemainingUses = owned[i].RemainingUses + }); + } + + SaveSystem.Save(save); + } + + // ── Model Event Handlers ────────────────────────────────── + + private void HandleTurnStarted(int turn) + { + gameInfoView.SetTurnText(turn, totalCategoryCount); + dicePanelView.ResetForNewTurn(); + dicePanelView.SetRollButtonState(true, 0, maxRollsPerTurn); + scoreCardView.ClearAllPreviews(); + } + + private void HandleRollComplete(int rollNumber) + { + bool canRollAgain = gameManager.CanRoll; + dicePanelView.SetRollButtonState(canRollAgain, rollNumber, maxRollsPerTurn); + dicePanelView.SetDiceInteractable(true); + + int[] values = diceManager.GetCurrentValues(); + dicePanelView.SetAllDiceValues(values); + + UpdatePreviewScores(values); + } + + private void HandleDieSettled(int index, int value) + { + dicePanelView.SetDieValue(index, value); + } + + private void HandleScored(YachtCategory category, int finalScore) + { + scoreCardView.SetCategoryScored(category, finalScore); + UpdateTotalDisplay(); + PerformSave(); + } + + private void HandleGameOver(int totalScore) + { + dicePanelView.SetRollButtonState(false, maxRollsPerTurn, maxRollsPerTurn); + dicePanelView.SetDiceInteractable(false); + scoreCardView.SetAllInteractable(false); + + int displayTotal = CalculateDisplayTotal(); + gameInfoView.ShowGameOver(displayTotal); + PerformSave(); + } + + // ── View Event Handlers ─────────────────────────────────── + + private void HandleRollClicked() + { + if (!gameManager.CanRoll) return; + + dicePanelView.SetRollButtonState(false, gameManager.CurrentRoll, maxRollsPerTurn); + dicePanelView.SetDiceInteractable(false); + scoreCardView.SetAllInteractable(false); + + gameManager.Roll(); + } + + private void HandleDiceToggled(int index) + { + if (gameManager.CurrentRoll == 0) return; + if (diceManager.IsAnyRolling) return; + + gameManager.ToggleDiceLock(index); + + bool isLocked = diceManager.IsLocked(index); + dicePanelView.SetDieLocked(index, isLocked); + } + + private void HandleCategorySelected(YachtCategory category) + { + if (!gameManager.CanScore) return; + if (scoringSystem.IsCategoryUsed(category)) return; + + gameManager.ScoreInCategory(category); + } + + private void HandleNewGameClicked() + { + gameInfoView.HideGameOver(); + scoreCardView.ResetAll(); + dicePanelView.ResetForNewGame(); + gameManager.StartNewGame(); + } + + private void HandleShopClicked() + { + if (shopController != null) + { + var shopView = shopController.GetComponentInChildren(true); + if (shopView != null) + { + if (shopView.IsVisible) shopView.Hide(); + else shopView.Show(); + } } } - } - private void HandleCurrencyChanged(int newBalance) - { - gameInfoView.SetCurrencyText(newBalance); - PerformSave(); - } - - private void HandleInventoryChangedForSave() - { - PerformSave(); - } - - // ── Helpers ──────────────────────────────────────────────── - - private void UpdatePreviewScores(int[] diceValues) - { - var previews = new Dictionary(); - var categories = (YachtCategory[])Enum.GetValues(typeof(YachtCategory)); - - for (int i = 0; i < categories.Length; i++) + private void HandleInventoryClicked() { - if (scoringSystem.IsCategoryUsed(categories[i])) continue; - - ScoreResult result = scoringSystem.PreviewScore(diceValues, categories[i]); - previews[categories[i]] = result.FinalScore; + if (inventoryController != null) + { + var inventoryView = inventoryController.GetComponentInChildren(true); + if (inventoryView != null) + { + if (inventoryView.IsVisible) inventoryView.Hide(); + else inventoryView.Show(); + } + } } - scoreCardView.UpdatePreviews(previews); - } - - private void UpdateTotalDisplay() - { - int upperSum = 0; - for (int i = 0; i < UpperCategories.Length; i++) + private void HandleCurrencyChanged(int newBalance) { - int catScore = scoringSystem.GetCategoryScore(UpperCategories[i]); - if (catScore >= 0) upperSum += catScore; + gameInfoView.SetCurrencyText(newBalance); + PerformSave(); } - bool hasBonus = upperSum >= UpperBonusThreshold; - int displayTotal = CalculateDisplayTotal(); - - scoreCardView.UpdateTotalDisplay(displayTotal, upperSum, hasBonus); - } - - private int CalculateDisplayTotal() - { - int total = scoringSystem.TotalScore; - - int upperSum = 0; - for (int i = 0; i < UpperCategories.Length; i++) + private void HandleInventoryChangedForSave() { - int catScore = scoringSystem.GetCategoryScore(UpperCategories[i]); - if (catScore >= 0) upperSum += catScore; + PerformSave(); } - if (upperSum >= UpperBonusThreshold) - total += UpperBonusValue; + // ── Helpers ──────────────────────────────────────────────── - return total; + private void UpdatePreviewScores(int[] diceValues) + { + var previews = new Dictionary(); + var categories = (YachtCategory[])Enum.GetValues(typeof(YachtCategory)); + + for (int i = 0; i < categories.Length; i++) + { + if (scoringSystem.IsCategoryUsed(categories[i])) continue; + + ScoreResult result = scoringSystem.PreviewScore(diceValues, categories[i]); + previews[categories[i]] = result.FinalScore; + } + + scoreCardView.UpdatePreviews(previews); + } + + private void UpdateTotalDisplay() + { + int upperSum = 0; + for (int i = 0; i < UpperCategories.Length; i++) + { + int catScore = scoringSystem.GetCategoryScore(UpperCategories[i]); + if (catScore >= 0) upperSum += catScore; + } + + bool hasBonus = upperSum >= UpperBonusThreshold; + int displayTotal = CalculateDisplayTotal(); + + scoreCardView.UpdateTotalDisplay(displayTotal, upperSum, hasBonus); + } + + private int CalculateDisplayTotal() + { + int total = scoringSystem.TotalScore; + + int upperSum = 0; + for (int i = 0; i < UpperCategories.Length; i++) + { + int catScore = scoringSystem.GetCategoryScore(UpperCategories[i]); + if (catScore >= 0) upperSum += catScore; + } + + if (upperSum >= UpperBonusThreshold) + total += UpperBonusValue; + + return total; + } } } -} diff --git a/Assets/Scripts/UI/GameInfoView.cs b/Assets/Scripts/UI/GameInfoView.cs index 5f27b04..9c74f84 100644 --- a/Assets/Scripts/UI/GameInfoView.cs +++ b/Assets/Scripts/UI/GameInfoView.cs @@ -5,69 +5,68 @@ using TMPro; namespace YachtDice.UI { - -public sealed class GameInfoView : MonoBehaviour -{ - [Header("Turn Info")] - [SerializeField] private TMP_Text turnText; - - [Header("Currency")] - [SerializeField] private TMP_Text currencyText; - - [Header("Navigation")] - [SerializeField] private Button shopButton; - [SerializeField] private Button inventoryButton; - - [Header("Game Over Overlay")] - [SerializeField] private GameObject gameOverPanel; - [SerializeField] private TMP_Text finalScoreText; - [SerializeField] private Button newGameButton; - - public event Action OnNewGameClicked; - public event Action OnShopClicked; - public event Action OnInventoryClicked; - - private void Awake() + public class GameInfoView : MonoBehaviour { - newGameButton.onClick.AddListener(() => OnNewGameClicked?.Invoke()); - gameOverPanel.SetActive(false); + [Header("Turn Info")] + [SerializeField] private TMP_Text turnText; - if (shopButton != null) - shopButton.onClick.AddListener(() => OnShopClicked?.Invoke()); - if (inventoryButton != null) - inventoryButton.onClick.AddListener(() => OnInventoryClicked?.Invoke()); - } + [Header("Currency")] + [SerializeField] private TMP_Text currencyText; - public void SetTurnText(int turn, int maxTurns) - { - turnText.text = $"Ход {turn} / {maxTurns}"; - } + [Header("Navigation")] + [SerializeField] private Button shopButton; + [SerializeField] private Button inventoryButton; - public void SetCurrencyText(int amount) - { - if (currencyText != null) - currencyText.text = amount.ToString(); - } + [Header("Game Over Overlay")] + [SerializeField] private GameObject gameOverPanel; + [SerializeField] private TMP_Text finalScoreText; + [SerializeField] private Button newGameButton; - public void ShowGameOver(int finalScore) - { - gameOverPanel.SetActive(true); - finalScoreText.text = $"Итого: {finalScore}"; - } + public event Action OnNewGameClicked; + public event Action OnShopClicked; + public event Action OnInventoryClicked; - public void HideGameOver() - { - gameOverPanel.SetActive(false); - } + private void Awake() + { + newGameButton.onClick.AddListener(() => OnNewGameClicked?.Invoke()); + gameOverPanel.SetActive(false); - private void OnDestroy() - { - newGameButton.onClick.RemoveAllListeners(); + if (shopButton != null) + shopButton.onClick.AddListener(() => OnShopClicked?.Invoke()); + if (inventoryButton != null) + inventoryButton.onClick.AddListener(() => OnInventoryClicked?.Invoke()); + } - if (shopButton != null) - shopButton.onClick.RemoveAllListeners(); - if (inventoryButton != null) - inventoryButton.onClick.RemoveAllListeners(); + public void SetTurnText(int turn, int maxTurns) + { + turnText.text = $"Ход {turn} / {maxTurns}"; + } + + public void SetCurrencyText(int amount) + { + if (currencyText != null) + currencyText.text = amount.ToString(); + } + + public void ShowGameOver(int finalScore) + { + gameOverPanel.SetActive(true); + finalScoreText.text = $"Итого: {finalScore}"; + } + + public void HideGameOver() + { + gameOverPanel.SetActive(false); + } + + private void OnDestroy() + { + newGameButton.onClick.RemoveAllListeners(); + + if (shopButton != null) + shopButton.onClick.RemoveAllListeners(); + if (inventoryButton != null) + inventoryButton.onClick.RemoveAllListeners(); + } } } -} diff --git a/Assets/Scripts/UI/ScoreCardView.cs b/Assets/Scripts/UI/ScoreCardView.cs index ce5797b..557b68e 100644 --- a/Assets/Scripts/UI/ScoreCardView.cs +++ b/Assets/Scripts/UI/ScoreCardView.cs @@ -6,109 +6,108 @@ using YachtDice.Scoring; namespace YachtDice.UI { - -public sealed class ScoreCardView : MonoBehaviour -{ - [Header("Category Rows (in YachtCategory enum order)")] - [SerializeField] private List categoryRows = new(); - - [Header("Summary")] - [SerializeField] private TMP_Text upperSumText; - [SerializeField] private TMP_Text upperBonusText; - [SerializeField] private TMP_Text totalScoreText; - - public event Action OnCategorySelected; - - private static readonly string[] CategoryNames = + public class ScoreCardView : MonoBehaviour { - "Единицы", - "Двойки", - "Тройки", - "Четвёрки", - "Пятёрки", - "Шестёрки", - "Тройка", - "Каре", - "Фулл-хаус", - "Малый стрит", - "Большой стрит", - "Яхта", - "Шанс" - }; + [Header("Category Rows (in YachtCategory enum order)")] + [SerializeField] private List categoryRows = new(); - private YachtCategory[] allCategories; + [Header("Summary")] + [SerializeField] private TMP_Text upperSumText; + [SerializeField] private TMP_Text upperBonusText; + [SerializeField] private TMP_Text totalScoreText; - private void Awake() - { - allCategories = (YachtCategory[])Enum.GetValues(typeof(YachtCategory)); + public event Action OnCategorySelected; - for (int i = 0; i < categoryRows.Count && i < allCategories.Length; i++) + private static readonly string[] CategoryNames = { - categoryRows[i].Initialize(allCategories[i], CategoryNames[i]); - categoryRows[i].OnCategorySelected += HandleCategorySelected; + "Единицы", + "Двойки", + "Тройки", + "Четвёрки", + "Пятёрки", + "Шестёрки", + "Тройка", + "Каре", + "Фулл-хаус", + "Малый стрит", + "Большой стрит", + "Яхта", + "Шанс" + }; + + private YachtCategory[] allCategories; + + private void Awake() + { + allCategories = (YachtCategory[])Enum.GetValues(typeof(YachtCategory)); + + for (int i = 0; i < categoryRows.Count && i < allCategories.Length; i++) + { + categoryRows[i].Initialize(allCategories[i], CategoryNames[i]); + categoryRows[i].OnCategorySelected += HandleCategorySelected; + } + + UpdateTotalDisplay(0, 0, false); } - UpdateTotalDisplay(0, 0, false); - } - - public void UpdatePreviews(Dictionary previews) - { - for (int i = 0; i < categoryRows.Count && i < allCategories.Length; i++) + public void UpdatePreviews(Dictionary previews) { - if (previews.TryGetValue(allCategories[i], out int preview)) + for (int i = 0; i < categoryRows.Count && i < allCategories.Length; i++) { - categoryRows[i].ShowPreview(preview); - categoryRows[i].SetInteractable(true); + if (previews.TryGetValue(allCategories[i], out int preview)) + { + categoryRows[i].ShowPreview(preview); + categoryRows[i].SetInteractable(true); + } } } - } - public void ClearAllPreviews() - { - for (int i = 0; i < categoryRows.Count; i++) + public void ClearAllPreviews() { - categoryRows[i].HidePreview(); - categoryRows[i].SetInteractable(false); + for (int i = 0; i < categoryRows.Count; i++) + { + categoryRows[i].HidePreview(); + categoryRows[i].SetInteractable(false); + } + } + + public void SetCategoryScored(YachtCategory category, int score) + { + int index = (int)category; + if (index >= 0 && index < categoryRows.Count) + categoryRows[index].SetRecordedScore(score); + } + + public void SetAllInteractable(bool interactable) + { + for (int i = 0; i < categoryRows.Count; i++) + categoryRows[i].SetInteractable(interactable); + } + + public void UpdateTotalDisplay(int totalScore, int upperSum, bool hasUpperBonus) + { + totalScoreText.text = totalScore.ToString(); + upperSumText.text = $"{upperSum} / 63"; + upperBonusText.text = hasUpperBonus ? "+35" : "---"; + } + + public void ResetAll() + { + for (int i = 0; i < categoryRows.Count; i++) + categoryRows[i].ResetRow(); + + UpdateTotalDisplay(0, 0, false); + } + + private void HandleCategorySelected(YachtCategory category) + { + OnCategorySelected?.Invoke(category); + } + + private void OnDestroy() + { + for (int i = 0; i < categoryRows.Count; i++) + categoryRows[i].OnCategorySelected -= HandleCategorySelected; } } - - public void SetCategoryScored(YachtCategory category, int score) - { - int index = (int)category; - if (index >= 0 && index < categoryRows.Count) - categoryRows[index].SetRecordedScore(score); - } - - public void SetAllInteractable(bool interactable) - { - for (int i = 0; i < categoryRows.Count; i++) - categoryRows[i].SetInteractable(interactable); - } - - public void UpdateTotalDisplay(int totalScore, int upperSum, bool hasUpperBonus) - { - totalScoreText.text = totalScore.ToString(); - upperSumText.text = $"{upperSum} / 63"; - upperBonusText.text = hasUpperBonus ? "+35" : "---"; - } - - public void ResetAll() - { - for (int i = 0; i < categoryRows.Count; i++) - categoryRows[i].ResetRow(); - - UpdateTotalDisplay(0, 0, false); - } - - private void HandleCategorySelected(YachtCategory category) - { - OnCategorySelected?.Invoke(category); - } - - private void OnDestroy() - { - for (int i = 0; i < categoryRows.Count; i++) - categoryRows[i].OnCategorySelected -= HandleCategorySelected; - } -} }