diff --git a/Assets/Scripts/Categories/Definition/CategoryCatalogSO.cs b/Assets/Scripts/Categories/Definition/CategoryCatalogSO.cs
new file mode 100644
index 0000000..4120902
--- /dev/null
+++ b/Assets/Scripts/Categories/Definition/CategoryCatalogSO.cs
@@ -0,0 +1,48 @@
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace YachtDice.Categories
+{
+ ///
+ /// Каталог всех доступных категорий.
+ /// Порядок определяет порядок отображения в UI.
+ /// Аналог ModifierCatalogSO.
+ ///
+ [CreateAssetMenu(fileName = "CategoryCatalog", menuName = "YachtDice/Categories/Catalog")]
+ public class CategoryCatalogSO : ScriptableObject
+ {
+ [SerializeField] private List categories = new();
+
+ public IReadOnlyList All => categories;
+ public int Count => categories.Count;
+
+ public CategoryDefinitionSO FindById(string id)
+ {
+ for (int i = 0; i < categories.Count; i++)
+ {
+ if (categories[i] != null && categories[i].Id == id)
+ return categories[i];
+ }
+ return null;
+ }
+
+ public int IndexOf(CategoryDefinitionSO def)
+ {
+ for (int i = 0; i < categories.Count; i++)
+ {
+ if (categories[i] == def)
+ return i;
+ }
+ return -1;
+ }
+
+#if UNITY_EDITOR
+ public static CategoryCatalogSO CreateForTest(List defs)
+ {
+ var catalog = CreateInstance();
+ catalog.categories = defs ?? new List();
+ return catalog;
+ }
+#endif
+ }
+}
diff --git a/Assets/Scripts/Categories/Definition/CategoryDefinitionSO.cs b/Assets/Scripts/Categories/Definition/CategoryDefinitionSO.cs
new file mode 100644
index 0000000..66e92c3
--- /dev/null
+++ b/Assets/Scripts/Categories/Definition/CategoryDefinitionSO.cs
@@ -0,0 +1,42 @@
+using System.Collections.Generic;
+using UnityEngine;
+using YachtDice.Dice;
+
+namespace YachtDice.Categories
+{
+ ///
+ /// Абстрактное определение категории для скоринга.
+ /// Каждая категория знает как вычислить очки по набору дайсов.
+ ///
+ public abstract class CategoryDefinitionSO : ScriptableObject
+ {
+ [Header("Identity")]
+ [SerializeField] private string id;
+ [SerializeField] private string displayName;
+ [SerializeField, TextArea] private string description;
+ [SerializeField] private Sprite icon;
+
+ [Header("Section")]
+ [SerializeField] private bool isUpperSection;
+
+ public string Id => id;
+ public string DisplayName => displayName;
+ public string Description => description;
+ public Sprite Icon => icon;
+ public bool IsUpperSection => isUpperSection;
+
+ ///
+ /// Вычисляет очки для данного набора дайсов.
+ ///
+ public abstract int Calculate(IReadOnlyList dice);
+
+#if UNITY_EDITOR
+ public void SetTestData(string testId, string testDisplayName, bool upperSection = false)
+ {
+ id = testId;
+ displayName = testDisplayName;
+ isUpperSection = upperSection;
+ }
+#endif
+ }
+}
diff --git a/Assets/Scripts/Categories/Definition/FullHouseCategorySO.cs b/Assets/Scripts/Categories/Definition/FullHouseCategorySO.cs
new file mode 100644
index 0000000..a209919
--- /dev/null
+++ b/Assets/Scripts/Categories/Definition/FullHouseCategorySO.cs
@@ -0,0 +1,34 @@
+using System.Collections.Generic;
+using UnityEngine;
+using YachtDice.Dice;
+
+namespace YachtDice.Categories
+{
+ ///
+ /// Категория Фулл-хаус: 3 одинаковых + 2 одинаковых.
+ /// При совпадении возвращает фиксированное число очков.
+ ///
+ [CreateAssetMenu(fileName = "FullHouseCategory", menuName = "YachtDice/Categories/Full House")]
+ public class FullHouseCategorySO : CategoryDefinitionSO
+ {
+ [Header("Scoring")]
+ [Tooltip("Фиксированное число очков за фулл-хаус")]
+ [SerializeField] private int fixedScore = 25;
+
+ public override int Calculate(IReadOnlyList dice)
+ {
+ int[] values = DiceCheckUtility.ExtractValues(dice);
+ return DiceCheckUtility.IsFullHouse(values) ? fixedScore : 0;
+ }
+
+#if UNITY_EDITOR
+ public static FullHouseCategorySO CreateForTest(string id, string displayName, int score = 25)
+ {
+ var so = CreateInstance();
+ so.SetTestData(id, displayName);
+ so.fixedScore = score;
+ return so;
+ }
+#endif
+ }
+}
diff --git a/Assets/Scripts/Categories/Definition/NOfAKindCategorySO.cs b/Assets/Scripts/Categories/Definition/NOfAKindCategorySO.cs
new file mode 100644
index 0000000..b22d72f
--- /dev/null
+++ b/Assets/Scripts/Categories/Definition/NOfAKindCategorySO.cs
@@ -0,0 +1,47 @@
+using System.Collections.Generic;
+using UnityEngine;
+using YachtDice.Dice;
+
+namespace YachtDice.Categories
+{
+ ///
+ /// Категория N-одинаковых: проверяет наличие N дайсов с одинаковым значением.
+ /// При успехе возвращает сумму всех дайсов или фиксированное число очков.
+ /// Используется для Тройки (3, сумма), Каре (4, сумма), Яхты (5, fixed=50).
+ ///
+ [CreateAssetMenu(fileName = "NOfAKindCategory", menuName = "YachtDice/Categories/N Of A Kind")]
+ public class NOfAKindCategorySO : CategoryDefinitionSO
+ {
+ [Header("Scoring")]
+ [Tooltip("Сколько одинаковых дайсов требуется")]
+ [SerializeField, Range(2, 6)] private int requiredCount = 3;
+
+ [Tooltip("Использовать фиксированное число очков вместо суммы")]
+ [SerializeField] private bool useFixedScore;
+
+ [Tooltip("Фиксированное число очков (если useFixedScore = true)")]
+ [SerializeField] private int fixedScore;
+
+ public override int Calculate(IReadOnlyList dice)
+ {
+ int[] values = DiceCheckUtility.ExtractValues(dice);
+
+ if (!DiceCheckUtility.NOfAKind(values, requiredCount))
+ return 0;
+
+ return useFixedScore ? fixedScore : DiceCheckUtility.Sum(values);
+ }
+
+#if UNITY_EDITOR
+ public static NOfAKindCategorySO CreateForTest(string id, string displayName, int count, bool fixedScoreMode = false, int score = 0)
+ {
+ var so = CreateInstance();
+ so.SetTestData(id, displayName);
+ so.requiredCount = count;
+ so.useFixedScore = fixedScoreMode;
+ so.fixedScore = score;
+ return so;
+ }
+#endif
+ }
+}
diff --git a/Assets/Scripts/Categories/Definition/StraightCategorySO.cs b/Assets/Scripts/Categories/Definition/StraightCategorySO.cs
new file mode 100644
index 0000000..069a5fb
--- /dev/null
+++ b/Assets/Scripts/Categories/Definition/StraightCategorySO.cs
@@ -0,0 +1,39 @@
+using System.Collections.Generic;
+using UnityEngine;
+using YachtDice.Dice;
+
+namespace YachtDice.Categories
+{
+ ///
+ /// Категория Стрит: последовательность заданной длины.
+ /// При совпадении возвращает фиксированное число очков.
+ /// Малый стрит: runLength=4, fixedScore=30. Большой стрит: runLength=5, fixedScore=40.
+ ///
+ [CreateAssetMenu(fileName = "StraightCategory", menuName = "YachtDice/Categories/Straight")]
+ public class StraightCategorySO : CategoryDefinitionSO
+ {
+ [Header("Scoring")]
+ [Tooltip("Требуемая длина последовательности")]
+ [SerializeField, Range(3, 6)] private int runLength = 4;
+
+ [Tooltip("Фиксированное число очков")]
+ [SerializeField] private int fixedScore = 30;
+
+ public override int Calculate(IReadOnlyList dice)
+ {
+ int[] values = DiceCheckUtility.ExtractValues(dice);
+ return DiceCheckUtility.HasStraightRun(values, runLength) ? fixedScore : 0;
+ }
+
+#if UNITY_EDITOR
+ public static StraightCategorySO CreateForTest(string id, string displayName, int run, int score)
+ {
+ var so = CreateInstance();
+ so.SetTestData(id, displayName);
+ so.runLength = run;
+ so.fixedScore = score;
+ return so;
+ }
+#endif
+ }
+}
diff --git a/Assets/Scripts/Categories/Definition/SumAllCategorySO.cs b/Assets/Scripts/Categories/Definition/SumAllCategorySO.cs
new file mode 100644
index 0000000..031464b
--- /dev/null
+++ b/Assets/Scripts/Categories/Definition/SumAllCategorySO.cs
@@ -0,0 +1,30 @@
+using System.Collections.Generic;
+using UnityEngine;
+using YachtDice.Dice;
+
+namespace YachtDice.Categories
+{
+ ///
+ /// Категория «Шанс»: суммирует все дайсы без условий.
+ ///
+ [CreateAssetMenu(fileName = "SumAllCategory", menuName = "YachtDice/Categories/Sum All (Chance)")]
+ public class SumAllCategorySO : CategoryDefinitionSO
+ {
+ public override int Calculate(IReadOnlyList dice)
+ {
+ int sum = 0;
+ for (int i = 0; i < dice.Count; i++)
+ sum += dice[i].Value;
+ return sum;
+ }
+
+#if UNITY_EDITOR
+ public static SumAllCategorySO CreateForTest(string id, string displayName)
+ {
+ var so = CreateInstance();
+ so.SetTestData(id, displayName);
+ return so;
+ }
+#endif
+ }
+}
diff --git a/Assets/Scripts/Categories/Definition/SumOfValueCategorySO.cs b/Assets/Scripts/Categories/Definition/SumOfValueCategorySO.cs
new file mode 100644
index 0000000..e10a415
--- /dev/null
+++ b/Assets/Scripts/Categories/Definition/SumOfValueCategorySO.cs
@@ -0,0 +1,38 @@
+using System.Collections.Generic;
+using UnityEngine;
+using YachtDice.Dice;
+
+namespace YachtDice.Categories
+{
+ ///
+ /// Категория верхней секции: суммирует все дайсы с заданным значением.
+ /// Используется для Единиц (1), Двоек (2), ... Шестёрок (6).
+ ///
+ [CreateAssetMenu(fileName = "SumOfValueCategory", menuName = "YachtDice/Categories/Sum Of Value")]
+ public class SumOfValueCategorySO : CategoryDefinitionSO
+ {
+ [Header("Scoring")]
+ [Tooltip("Значение грани для суммирования (1-6)")]
+ [SerializeField, Range(1, 6)] private int targetValue = 1;
+
+ public int TargetValue => targetValue;
+
+ public override int Calculate(IReadOnlyList dice)
+ {
+ int sum = 0;
+ for (int i = 0; i < dice.Count; i++)
+ if (dice[i].Value == targetValue) sum += targetValue;
+ return sum;
+ }
+
+#if UNITY_EDITOR
+ public static SumOfValueCategorySO CreateForTest(string id, string displayName, int target)
+ {
+ var so = CreateInstance();
+ so.SetTestData(id, displayName, upperSection: true);
+ so.targetValue = target;
+ return so;
+ }
+#endif
+ }
+}
diff --git a/Assets/Scripts/Categories/DiceCheckUtility.cs b/Assets/Scripts/Categories/DiceCheckUtility.cs
new file mode 100644
index 0000000..05e4417
--- /dev/null
+++ b/Assets/Scripts/Categories/DiceCheckUtility.cs
@@ -0,0 +1,78 @@
+using System;
+using System.Collections.Generic;
+using YachtDice.Dice;
+
+namespace YachtDice.Categories
+{
+ ///
+ /// Статические хелперы для проверки комбинаций дайсов.
+ /// Перенесены из CategoryScorer для переиспользования в конкретных SO-категориях.
+ ///
+ public static class DiceCheckUtility
+ {
+ /// Извлекает массив значений из абстрактных дайсов.
+ public static int[] ExtractValues(IReadOnlyList dice)
+ {
+ int[] values = new int[dice.Count];
+ for (int i = 0; i < dice.Count; i++)
+ values[i] = dice[i].Value;
+ return values;
+ }
+
+ /// Сумма дайсов, показывающих конкретное значение.
+ public static int SumOfValue(int[] values, int target)
+ {
+ int sum = 0;
+ for (int i = 0; i < values.Length; i++)
+ if (values[i] == target) sum += target;
+ return sum;
+ }
+
+ /// Сумма всех дайсов.
+ public static int Sum(int[] values)
+ {
+ int sum = 0;
+ for (int i = 0; i < values.Length; i++) sum += values[i];
+ return sum;
+ }
+
+ /// Есть ли N или более одинаковых значений.
+ public static bool NOfAKind(int[] values, int n)
+ {
+ int[] counts = new int[7];
+ for (int i = 0; i < values.Length; i++) counts[values[i]]++;
+ for (int v = 1; v <= 6; v++)
+ if (counts[v] >= n) return true;
+ return false;
+ }
+
+ /// Проверяет фулл-хаус (3 + 2 одинаковых).
+ public static bool IsFullHouse(int[] values)
+ {
+ int[] counts = new int[7];
+ for (int i = 0; i < values.Length; i++) counts[values[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;
+ }
+
+ /// Есть ли последовательность заданной длины.
+ public static bool HasStraightRun(int[] values, int runLength)
+ {
+ bool[] present = new bool[7];
+ for (int i = 0; i < values.Length; i++) present[values[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;
+ }
+ }
+}
diff --git a/Assets/Scripts/DI/GameLifetimeScope.cs b/Assets/Scripts/DI/GameLifetimeScope.cs
index e4367cb..4bdb6de 100644
--- a/Assets/Scripts/DI/GameLifetimeScope.cs
+++ b/Assets/Scripts/DI/GameLifetimeScope.cs
@@ -1,6 +1,7 @@
using UnityEngine;
using VContainer;
using VContainer.Unity;
+using YachtDice.Categories;
using YachtDice.Economy;
using YachtDice.Events;
using YachtDice.Game;
@@ -14,6 +15,7 @@ namespace YachtDice.DI
public class GameLifetimeScope : LifetimeScope
{
[SerializeField] private ModifierCatalogSO modifierCatalog;
+ [SerializeField] private CategoryCatalogSO categoryCatalog;
[Header("Scene References")]
[SerializeField] private ScoringSystem scoringSystem;
@@ -23,8 +25,9 @@ namespace YachtDice.DI
protected override void Configure(IContainerBuilder builder)
{
- // SO catalog
+ // SO catalogs
builder.RegisterInstance(modifierCatalog);
+ builder.RegisterInstance(categoryCatalog);
// Core modifier services
builder.Register(Lifetime.Singleton);
@@ -38,4 +41,4 @@ namespace YachtDice.DI
builder.RegisterComponent(diceManager);
}
}
-}
\ No newline at end of file
+}
diff --git a/Assets/Scripts/Dice/DiceRoller.cs b/Assets/Scripts/Dice/DiceRoller.cs
index 9c9be7b..8506779 100644
--- a/Assets/Scripts/Dice/DiceRoller.cs
+++ b/Assets/Scripts/Dice/DiceRoller.cs
@@ -14,6 +14,10 @@ namespace YachtDice.Dice
[Header("References")]
[SerializeField] private Dice dice;
[SerializeField] private Rigidbody rb;
+ [SerializeField] private DieDefinitionSO definition;
+
+ /// Определение типа дайса (назначается в инспекторе).
+ public DieDefinitionSO Definition => definition;
[Header("Throw Settings")]
[Tooltip("Сила подброса вверх")]
diff --git a/Assets/Scripts/Dice/DieDefinitionSO.cs b/Assets/Scripts/Dice/DieDefinitionSO.cs
new file mode 100644
index 0000000..789d755
--- /dev/null
+++ b/Assets/Scripts/Dice/DieDefinitionSO.cs
@@ -0,0 +1,36 @@
+using UnityEngine;
+
+namespace YachtDice.Dice
+{
+ ///
+ /// Абстрактное определение типа дайса.
+ /// Наследники описывают конкретные виды (стандартный d6, специальные и т.д.).
+ ///
+ public abstract class DieDefinitionSO : ScriptableObject
+ {
+ [Header("Identity")]
+ [SerializeField] private string id;
+ [SerializeField] private string displayName;
+ [SerializeField] private Sprite icon;
+
+ public string Id => id;
+ public string DisplayName => displayName;
+ public Sprite Icon => icon;
+
+ /// Количество граней.
+ public abstract int FaceCount { get; }
+
+ /// Возвращает массив всех возможных значений граней.
+ public abstract int[] GetFaceValues();
+
+#if UNITY_EDITOR
+ public static T CreateForTest(string id, string displayName = null) where T : DieDefinitionSO
+ {
+ var so = CreateInstance();
+ so.id = id;
+ so.displayName = displayName ?? id;
+ return so;
+ }
+#endif
+ }
+}
diff --git a/Assets/Scripts/Dice/DieInstance.cs b/Assets/Scripts/Dice/DieInstance.cs
new file mode 100644
index 0000000..4136957
--- /dev/null
+++ b/Assets/Scripts/Dice/DieInstance.cs
@@ -0,0 +1,27 @@
+namespace YachtDice.Dice
+{
+ ///
+ /// Рантайм-состояние одного дайса.
+ /// Хранит текущее значение верхней грани и ссылку на определение типа.
+ ///
+ public class DieInstance : IDie
+ {
+ public DieDefinitionSO Definition { get; }
+ public int Value { get; set; }
+ public bool IsLocked { get; set; }
+
+ public DieInstance(DieDefinitionSO definition)
+ {
+ Definition = definition;
+ Value = 0;
+ IsLocked = false;
+ }
+
+ public DieInstance(DieDefinitionSO definition, int initialValue)
+ {
+ Definition = definition;
+ Value = initialValue;
+ IsLocked = false;
+ }
+ }
+}
diff --git a/Assets/Scripts/Dice/IDie.cs b/Assets/Scripts/Dice/IDie.cs
new file mode 100644
index 0000000..073d8a9
--- /dev/null
+++ b/Assets/Scripts/Dice/IDie.cs
@@ -0,0 +1,15 @@
+namespace YachtDice.Dice
+{
+ ///
+ /// Минимальный контракт для любого дайса.
+ /// Каждый дайс всегда имеет текущее значение (верхняя грань) и определение типа.
+ ///
+ public interface IDie
+ {
+ /// Текущее значение верхней грани.
+ int Value { get; }
+
+ /// Определение типа дайса (ScriptableObject).
+ DieDefinitionSO Definition { get; }
+ }
+}
diff --git a/Assets/Scripts/Dice/StandardDieSO.cs b/Assets/Scripts/Dice/StandardDieSO.cs
new file mode 100644
index 0000000..f192a21
--- /dev/null
+++ b/Assets/Scripts/Dice/StandardDieSO.cs
@@ -0,0 +1,33 @@
+using UnityEngine;
+
+namespace YachtDice.Dice
+{
+ ///
+ /// Стандартный дайс с настраиваемыми значениями граней.
+ /// По умолчанию — классический d6 (1-6).
+ ///
+ [CreateAssetMenu(fileName = "StandardDie", menuName = "YachtDice/Dice/Standard Die")]
+ public class StandardDieSO : DieDefinitionSO
+ {
+ [Header("Configuration")]
+ [SerializeField] private int[] faceValues = { 1, 2, 3, 4, 5, 6 };
+
+ public override int FaceCount => faceValues.Length;
+
+ public override int[] GetFaceValues()
+ {
+ int[] copy = new int[faceValues.Length];
+ System.Array.Copy(faceValues, copy, faceValues.Length);
+ return copy;
+ }
+
+#if UNITY_EDITOR
+ public static StandardDieSO CreateStandardD6ForTest()
+ {
+ var so = CreateForTest("standard_d6", "Стандартный d6");
+ so.faceValues = new[] { 1, 2, 3, 4, 5, 6 };
+ return so;
+ }
+#endif
+ }
+}
diff --git a/Assets/Scripts/Game/DiceManager.cs b/Assets/Scripts/Game/DiceManager.cs
index f7eec67..ba4114b 100644
--- a/Assets/Scripts/Game/DiceManager.cs
+++ b/Assets/Scripts/Game/DiceManager.cs
@@ -14,32 +14,37 @@ namespace YachtDice.Game
public int DiceCount => diceRollers.Count;
- private bool[] locked;
- private int[] currentValues;
+ private DieInstance[] diceInstances;
private int pendingCount;
private void Awake()
{
int count = diceRollers.Count;
- locked = new bool[count];
- currentValues = new int[count];
+ diceInstances = new DieInstance[count];
+
+ for (int i = 0; i < count; i++)
+ {
+ var definition = diceRollers[i].Definition;
+ diceInstances[i] = new DieInstance(definition);
+ }
}
- public bool IsLocked(int index) => locked[index];
+ public bool IsLocked(int index) => diceInstances[index].IsLocked;
public void ToggleLock(int index)
{
- locked[index] = !locked[index];
+ diceInstances[index].IsLocked = !diceInstances[index].IsLocked;
}
public void SetLocked(int index, bool isLocked)
{
- locked[index] = isLocked;
+ diceInstances[index].IsLocked = isLocked;
}
public void UnlockAll()
{
- for (int i = 0; i < locked.Length; i++) locked[i] = false;
+ for (int i = 0; i < diceInstances.Length; i++)
+ diceInstances[i].IsLocked = false;
}
public void RollUnlocked()
@@ -51,7 +56,7 @@ namespace YachtDice.Game
for (int i = 0; i < diceRollers.Count; i++)
{
- if (locked[i]) continue;
+ if (diceInstances[i].IsLocked) continue;
pendingCount++;
int capturedIndex = i;
@@ -59,7 +64,7 @@ namespace YachtDice.Game
void Handler(int value)
{
diceRollers[capturedIndex].OnRollFinished -= Handler;
- currentValues[capturedIndex] = value;
+ diceInstances[capturedIndex].Value = value;
OnDieSettled?.Invoke(capturedIndex, value);
pendingCount--;
@@ -75,14 +80,19 @@ namespace YachtDice.Game
OnAllDiceSettled?.Invoke();
}
+ /// Возвращает абстрактный список дайсов (основной API).
+ public IReadOnlyList GetDice() => diceInstances;
+
+ /// Возвращает копию текущих значений (обратная совместимость).
public int[] GetCurrentValues()
{
- int[] copy = new int[currentValues.Length];
- Array.Copy(currentValues, copy, currentValues.Length);
- return copy;
+ int[] values = new int[diceInstances.Length];
+ for (int i = 0; i < diceInstances.Length; i++)
+ values[i] = diceInstances[i].Value;
+ return values;
}
- public int GetValue(int index) => currentValues[index];
+ public int GetValue(int index) => diceInstances[index].Value;
public bool IsAnyRolling
{
@@ -100,7 +110,7 @@ namespace YachtDice.Game
{
var diceComponent = diceRollers[i].GetComponent();
if (diceComponent != null && diceComponent.TryGetTopValue(out int val))
- currentValues[i] = val;
+ diceInstances[i].Value = val;
}
}
}
diff --git a/Assets/Scripts/Game/GameManager.cs b/Assets/Scripts/Game/GameManager.cs
index 8c82cc9..196cbf1 100644
--- a/Assets/Scripts/Game/GameManager.cs
+++ b/Assets/Scripts/Game/GameManager.cs
@@ -1,5 +1,6 @@
using System;
using UnityEngine;
+using YachtDice.Categories;
using YachtDice.Scoring;
namespace YachtDice.Game
@@ -22,7 +23,7 @@ namespace YachtDice.Game
public event Action OnTurnStarted;
public event Action OnRollComplete;
- public event Action OnScored;
+ public event Action OnScored;
public event Action OnGameOver;
private void Start()
@@ -75,15 +76,15 @@ namespace YachtDice.Game
Debug.Log($"Dice {index + 1} (value={diceManager.GetValue(index)}): {(isLocked ? "LOCKED" : "UNLOCKED")}");
}
- public void ScoreInCategory(YachtCategory category)
+ public void ScoreInCategory(CategoryDefinitionSO category)
{
if (!CanScore) return;
if (scoringSystem.IsCategoryUsed(category)) return;
- int[] values = diceManager.GetCurrentValues();
- ScoreResult result = scoringSystem.ScoreCategory(values, category);
+ var dice = diceManager.GetDice();
+ ScoreResult result = scoringSystem.ScoreCategory(dice, category);
- Debug.Log($"Scored {category}: base={result.BaseScore}, " +
+ Debug.Log($"Scored {category.DisplayName}: base={result.BaseScore}, " +
$"bonus=+{result.FlatBonus}, mult=x{result.Multiplier:F1}, " +
$"FINAL={result.FinalScore} | Total={scoringSystem.TotalScore}");
diff --git a/Assets/Scripts/Inventory/InventoryController.cs b/Assets/Scripts/Inventory/InventoryController.cs
index 33dd263..6e620d0 100644
--- a/Assets/Scripts/Inventory/InventoryController.cs
+++ b/Assets/Scripts/Inventory/InventoryController.cs
@@ -1,4 +1,5 @@
using UnityEngine;
+using YachtDice.Categories;
using YachtDice.Economy;
using YachtDice.Modifiers.Runtime;
using YachtDice.Scoring;
@@ -73,7 +74,7 @@ namespace YachtDice.Inventory
RefreshView();
}
- private void HandleCategoryConfirmed(YachtCategory category, ScoreResult result)
+ private void HandleCategoryConfirmed(CategoryDefinitionSO category, ScoreResult result)
{
model.ConsumeUseOnActive();
}
diff --git a/Assets/Scripts/Modifiers/Conditions/CategoryCondition.cs b/Assets/Scripts/Modifiers/Conditions/CategoryCondition.cs
index 98612da..486345f 100644
--- a/Assets/Scripts/Modifiers/Conditions/CategoryCondition.cs
+++ b/Assets/Scripts/Modifiers/Conditions/CategoryCondition.cs
@@ -1,15 +1,15 @@
using UnityEngine;
+using YachtDice.Categories;
using YachtDice.Modifiers.Core;
using YachtDice.Modifiers.Definition;
using YachtDice.Modifiers.Runtime;
-using YachtDice.Scoring;
namespace YachtDice.Modifiers.Conditions
{
[CreateAssetMenu(fileName = "CategoryCondition", menuName = "YachtDice/Modifiers/Conditions/Category")]
public class CategoryCondition : ConditionSO
{
- [SerializeField] private YachtCategory requiredCategory;
+ [SerializeField] private CategoryDefinitionSO requiredCategory;
public override bool Evaluate(ModifierContext context, ModifierInstance instance)
{
@@ -17,7 +17,7 @@ namespace YachtDice.Modifiers.Conditions
}
#if UNITY_EDITOR
- public static CategoryCondition CreateForTest(YachtCategory category)
+ public static CategoryCondition CreateForTest(CategoryDefinitionSO category)
{
var so = CreateInstance();
so.requiredCategory = category;
diff --git a/Assets/Scripts/Modifiers/Core/ModifierContext.cs b/Assets/Scripts/Modifiers/Core/ModifierContext.cs
index bd5addf..a01ffb9 100644
--- a/Assets/Scripts/Modifiers/Core/ModifierContext.cs
+++ b/Assets/Scripts/Modifiers/Core/ModifierContext.cs
@@ -1,5 +1,7 @@
using System.Collections.Generic;
using UnityEngine;
+using YachtDice.Categories;
+using YachtDice.Dice;
using YachtDice.Modifiers.Runtime;
using YachtDice.Scoring;
@@ -12,8 +14,15 @@ namespace YachtDice.Modifiers.Core
public int FlatBonus;
public float Multiplier = 1f;
public float PostMultiplier = 1f;
+
+ /// Абстрактные дайсы (основной API).
+ public IReadOnlyList Dice;
+
+ /// Значения дайсов (обратная совместимость с существующими модификаторами).
public int[] DiceValues;
- public YachtCategory Category;
+
+ /// Категория, в которую записывается результат.
+ public CategoryDefinitionSO Category;
// Game state (read-only snapshot)
public int CurrentRoll;
@@ -46,8 +55,8 @@ namespace YachtDice.Modifiers.Core
public static ModifierContext CreateForScoring(
int baseScore,
- int[] diceValues,
- YachtCategory category,
+ IReadOnlyList dice,
+ CategoryDefinitionSO category,
int currentRoll,
int currentTurn,
int playerCurrency,
@@ -56,7 +65,8 @@ namespace YachtDice.Modifiers.Core
return new ModifierContext
{
BaseScore = baseScore,
- DiceValues = diceValues,
+ Dice = dice,
+ DiceValues = DiceCheckUtility.ExtractValues(dice),
Category = category,
CurrentRoll = currentRoll,
CurrentTurn = currentTurn,
diff --git a/Assets/Scripts/Scoring/CategoryScorer.cs b/Assets/Scripts/Scoring/CategoryScorer.cs
deleted file mode 100644
index 0b66d3c..0000000
--- a/Assets/Scripts/Scoring/CategoryScorer.cs
+++ /dev/null
@@ -1,82 +0,0 @@
-using System;
-
-namespace YachtDice.Scoring
-{
- public static class CategoryScorer
- {
- public static int Calculate(int[] dice, YachtCategory category)
- {
- if (dice == null || dice.Length != 5)
- throw new ArgumentException("Exactly 5 dice values required.");
-
- 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
- };
- }
-
- 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 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;
- }
- }
-}
diff --git a/Assets/Scripts/Scoring/CategoryScorer.cs.meta b/Assets/Scripts/Scoring/CategoryScorer.cs.meta
deleted file mode 100644
index afefd3e..0000000
--- a/Assets/Scripts/Scoring/CategoryScorer.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: 2fa2cb346fa82b846a3cf52c47ca9cda
\ No newline at end of file
diff --git a/Assets/Scripts/Scoring/ScoreResult.cs b/Assets/Scripts/Scoring/ScoreResult.cs
index 3f34fe9..88751f3 100644
--- a/Assets/Scripts/Scoring/ScoreResult.cs
+++ b/Assets/Scripts/Scoring/ScoreResult.cs
@@ -1,5 +1,8 @@
using System;
+using System.Collections.Generic;
using UnityEngine;
+using YachtDice.Categories;
+using YachtDice.Dice;
namespace YachtDice.Scoring
{
@@ -10,18 +13,18 @@ namespace YachtDice.Scoring
public int FlatBonus;
public float Multiplier;
public int[] DiceValues;
- public YachtCategory Category;
+ public CategoryDefinitionSO Category;
public int FinalScore => Mathf.FloorToInt((BaseScore + FlatBonus) * Multiplier);
- public static ScoreResult Create(int baseScore, int[] diceValues, YachtCategory category)
+ public static ScoreResult Create(int baseScore, IReadOnlyList dice, CategoryDefinitionSO category)
{
return new ScoreResult
{
BaseScore = baseScore,
FlatBonus = 0,
Multiplier = 1f,
- DiceValues = diceValues,
+ DiceValues = DiceCheckUtility.ExtractValues(dice),
Category = category
};
}
diff --git a/Assets/Scripts/Scoring/ScoringSystem.cs b/Assets/Scripts/Scoring/ScoringSystem.cs
index b44a216..a257338 100644
--- a/Assets/Scripts/Scoring/ScoringSystem.cs
+++ b/Assets/Scripts/Scoring/ScoringSystem.cs
@@ -3,6 +3,8 @@ using System.Collections.Generic;
using Cysharp.Threading.Tasks;
using UnityEngine;
using VContainer;
+using YachtDice.Categories;
+using YachtDice.Dice;
using YachtDice.Events;
using YachtDice.Modifiers.Core;
using YachtDice.Modifiers.Runtime;
@@ -11,26 +13,30 @@ namespace YachtDice.Scoring
{
public class ScoringSystem : MonoBehaviour
{
- public event Action OnCategoryScored;
+ public event Action OnCategoryScored;
public event Action OnAllCategoriesScored;
- public event Action OnCategoryConfirmed;
+ public event Action OnCategoryConfirmed;
- private readonly Dictionary scorecard = new();
- private readonly HashSet usedCategories = new();
+ private readonly Dictionary scorecard = new();
+ private readonly HashSet usedCategories = new();
private GameEventBus eventBus;
private ModifierRegistry modifierRegistry;
+ private CategoryCatalogSO catalog;
[Inject]
- public void Construct(GameEventBus eventBus, ModifierRegistry modifierRegistry)
+ public void Construct(GameEventBus eventBus, ModifierRegistry modifierRegistry, CategoryCatalogSO catalog)
{
this.eventBus = eventBus;
this.modifierRegistry = modifierRegistry;
+ this.catalog = catalog;
}
- public bool IsCategoryUsed(YachtCategory category) => usedCategories.Contains(category);
+ public CategoryCatalogSO Catalog => catalog;
- public int GetCategoryScore(YachtCategory category)
+ public bool IsCategoryUsed(CategoryDefinitionSO category) => usedCategories.Contains(category);
+
+ public int GetCategoryScore(CategoryDefinitionSO category)
{
return scorecard.TryGetValue(category, out int score) ? score : -1;
}
@@ -47,20 +53,20 @@ namespace YachtDice.Scoring
public int CategoriesFilledCount => usedCategories.Count;
- public int TotalCategoryCount => Enum.GetValues(typeof(YachtCategory)).Length;
+ public int TotalCategoryCount => catalog != null ? catalog.Count : 0;
public bool IsComplete => CategoriesFilledCount >= TotalCategoryCount;
- public ScoreResult PreviewScore(int[] diceValues, YachtCategory category,
+ public ScoreResult PreviewScore(IReadOnlyList dice, CategoryDefinitionSO category,
int currentRoll = 0, int currentTurn = 0, int playerCurrency = 0)
{
- int baseScore = CategoryScorer.Calculate(diceValues, category);
+ int baseScore = category.Calculate(dice);
if (eventBus == null || modifierRegistry == null)
- return ScoreResult.Create(baseScore, diceValues, category);
+ return ScoreResult.Create(baseScore, dice, category);
var context = ModifierContext.CreateForScoring(
- baseScore, diceValues, category,
+ baseScore, dice, category,
currentRoll, currentTurn, playerCurrency,
modifierRegistry.Active);
@@ -69,19 +75,19 @@ namespace YachtDice.Scoring
return context.ToScoreResult();
}
- public async UniTask ScoreCategoryAsync(int[] diceValues, YachtCategory category,
+ public async UniTask ScoreCategoryAsync(IReadOnlyList dice, CategoryDefinitionSO category,
int currentRoll, int currentTurn, int playerCurrency)
{
if (usedCategories.Contains(category))
- throw new InvalidOperationException($"Category {category} has already been scored.");
+ throw new InvalidOperationException($"Category {category.DisplayName} has already been scored.");
- int baseScore = CategoryScorer.Calculate(diceValues, category);
+ int baseScore = category.Calculate(dice);
ModifierContext context;
if (eventBus != null && modifierRegistry != null)
{
context = ModifierContext.CreateForScoring(
- baseScore, diceValues, category,
+ baseScore, dice, category,
currentRoll, currentTurn, playerCurrency,
modifierRegistry.Active);
@@ -92,7 +98,8 @@ namespace YachtDice.Scoring
context = new ModifierContext
{
BaseScore = baseScore,
- DiceValues = diceValues,
+ Dice = dice,
+ DiceValues = DiceCheckUtility.ExtractValues(dice),
Category = category,
};
}
@@ -111,18 +118,18 @@ namespace YachtDice.Scoring
return result;
}
- public ScoreResult ScoreCategory(int[] diceValues, YachtCategory category)
+ public ScoreResult ScoreCategory(IReadOnlyList dice, CategoryDefinitionSO category)
{
if (usedCategories.Contains(category))
- throw new InvalidOperationException($"Category {category} has already been scored.");
+ throw new InvalidOperationException($"Category {category.DisplayName} has already been scored.");
- int baseScore = CategoryScorer.Calculate(diceValues, category);
+ int baseScore = category.Calculate(dice);
ModifierContext context = null;
if (eventBus != null && modifierRegistry != null)
{
context = ModifierContext.CreateForScoring(
- baseScore, diceValues, category,
+ baseScore, dice, category,
0, 0, 0,
modifierRegistry.Active);
@@ -133,7 +140,7 @@ namespace YachtDice.Scoring
if (context != null)
result = context.ToScoreResult();
else
- result = ScoreResult.Create(baseScore, diceValues, category);
+ result = ScoreResult.Create(baseScore, dice, category);
int finalScore = result.FinalScore;
scorecard[category] = finalScore;
diff --git a/Assets/Scripts/Scoring/YachtCategory.cs b/Assets/Scripts/Scoring/YachtCategory.cs
deleted file mode 100644
index 9f7bacf..0000000
--- a/Assets/Scripts/Scoring/YachtCategory.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-namespace YachtDice.Scoring
-{
- public enum YachtCategory
- {
- // Upper Section
- Ones,
- Twos,
- Threes,
- Fours,
- Fives,
- Sixes,
-
- // Lower Section
- ThreeOfAKind,
- FourOfAKind,
- FullHouse,
- SmallStraight,
- LargeStraight,
- Yacht,
- Chance
- }
-}
diff --git a/Assets/Scripts/Scoring/YachtCategory.cs.meta b/Assets/Scripts/Scoring/YachtCategory.cs.meta
deleted file mode 100644
index 7160118..0000000
--- a/Assets/Scripts/Scoring/YachtCategory.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: b90683b5d8ef0d24c8c050d7fd0eb75d
\ No newline at end of file
diff --git a/Assets/Scripts/Tests/Editor/ModifierEffectTests.cs b/Assets/Scripts/Tests/Editor/ModifierEffectTests.cs
index c09ae42..e43e949 100644
--- a/Assets/Scripts/Tests/Editor/ModifierEffectTests.cs
+++ b/Assets/Scripts/Tests/Editor/ModifierEffectTests.cs
@@ -1,5 +1,6 @@
using NUnit.Framework;
using UnityEngine;
+using YachtDice.Categories;
using YachtDice.Modifiers.Core;
using YachtDice.Modifiers.Definition;
using YachtDice.Modifiers.Effects;
@@ -10,19 +11,33 @@ namespace YachtDice.Tests
{
public class ModifierEffectTests
{
+ private CategoryDefinitionSO testCategory;
+
+ [SetUp]
+ public void SetUp()
+ {
+ testCategory = SumAllCategorySO.CreateForTest("chance", "Шанс");
+ }
+
+ [TearDown]
+ public void TearDown()
+ {
+ Object.DestroyImmediate(testCategory);
+ }
+
private ModifierInstance CreateInstance(string id = "test")
{
var def = ModifierDefinitionSO.CreateForTest(id, null);
return new ModifierInstance(def);
}
- private ModifierContext CreateContext(int baseScore, int[] dice, YachtCategory category)
+ private ModifierContext CreateContext(int baseScore, int[] dice)
{
return new ModifierContext
{
BaseScore = baseScore,
DiceValues = dice,
- Category = category,
+ Category = testCategory,
};
}
@@ -32,7 +47,7 @@ namespace YachtDice.Tests
public void AddPerDieEffect_CountsMatchingDice()
{
var effect = AddPerDieEffect.CreateForTest(10, targetDieValue: 1);
- var ctx = CreateContext(5, new[] { 1, 1, 3, 4, 1 }, YachtCategory.Ones);
+ var ctx = CreateContext(5, new[] { 1, 1, 3, 4, 1 });
var inst = CreateInstance();
effect.Apply(ctx, inst).GetAwaiter().GetResult();
@@ -44,7 +59,7 @@ namespace YachtDice.Tests
public void AddPerDieEffect_ZeroTarget_CountsAllDice()
{
var effect = AddPerDieEffect.CreateForTest(2, targetDieValue: 0);
- var ctx = CreateContext(10, new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance);
+ var ctx = CreateContext(10, new[] { 1, 2, 3, 4, 5 });
var inst = CreateInstance();
effect.Apply(ctx, inst).GetAwaiter().GetResult();
@@ -56,7 +71,7 @@ namespace YachtDice.Tests
public void AddPerDieEffect_NoMatches_ZeroBonus()
{
var effect = AddPerDieEffect.CreateForTest(10, targetDieValue: 6);
- var ctx = CreateContext(5, new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance);
+ var ctx = CreateContext(5, new[] { 1, 2, 3, 4, 5 });
var inst = CreateInstance();
effect.Apply(ctx, inst).GetAwaiter().GetResult();
@@ -68,7 +83,7 @@ namespace YachtDice.Tests
public void AddPerDieEffect_ScalesWithStacks()
{
var effect = AddPerDieEffect.CreateForTest(10, targetDieValue: 1);
- var ctx = CreateContext(5, new[] { 1, 1, 3, 4, 1 }, YachtCategory.Ones);
+ var ctx = CreateContext(5, new[] { 1, 1, 3, 4, 1 });
var inst = CreateInstance();
inst.Stacks = 2;
@@ -83,7 +98,7 @@ namespace YachtDice.Tests
public void AddFlatScoreEffect_AddsFlat()
{
var effect = AddFlatScoreEffect.CreateForTest(15);
- var ctx = CreateContext(25, new[] { 3, 3, 2, 2, 2 }, YachtCategory.FullHouse);
+ var ctx = CreateContext(25, new[] { 3, 3, 2, 2, 2 });
var inst = CreateInstance();
effect.Apply(ctx, inst).GetAwaiter().GetResult();
@@ -95,7 +110,7 @@ namespace YachtDice.Tests
public void AddFlatScoreEffect_ScalesWithStacks()
{
var effect = AddFlatScoreEffect.CreateForTest(15);
- var ctx = CreateContext(25, new[] { 3, 3, 2, 2, 2 }, YachtCategory.FullHouse);
+ var ctx = CreateContext(25, new[] { 3, 3, 2, 2, 2 });
var inst = CreateInstance();
inst.Stacks = 3;
@@ -110,7 +125,7 @@ namespace YachtDice.Tests
public void MultiplyPerDieEffect_MultipliesPerMatch()
{
var effect = MultiplyPerDieEffect.CreateForTest(2f, targetDieValue: 6);
- var ctx = CreateContext(18, new[] { 6, 6, 6, 1, 2 }, YachtCategory.Sixes);
+ var ctx = CreateContext(18, new[] { 6, 6, 6, 1, 2 });
var inst = CreateInstance();
effect.Apply(ctx, inst).GetAwaiter().GetResult();
@@ -122,7 +137,7 @@ namespace YachtDice.Tests
public void MultiplyPerDieEffect_NoMatches_MultiplierUnchanged()
{
var effect = MultiplyPerDieEffect.CreateForTest(3f, targetDieValue: 6);
- var ctx = CreateContext(10, new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance);
+ var ctx = CreateContext(10, new[] { 1, 2, 3, 4, 5 });
var inst = CreateInstance();
effect.Apply(ctx, inst).GetAwaiter().GetResult();
@@ -136,7 +151,7 @@ namespace YachtDice.Tests
public void MultiplyScoreEffect_MultipliesOnce()
{
var effect = MultiplyScoreEffect.CreateForTest(1.5f);
- var ctx = CreateContext(50, new[] { 6, 6, 6, 6, 6 }, YachtCategory.Yacht);
+ var ctx = CreateContext(50, new[] { 6, 6, 6, 6, 6 });
var inst = CreateInstance();
effect.Apply(ctx, inst).GetAwaiter().GetResult();
@@ -148,7 +163,7 @@ namespace YachtDice.Tests
public void MultiplyScoreEffect_ScalesWithStacks()
{
var effect = MultiplyScoreEffect.CreateForTest(2f);
- var ctx = CreateContext(10, new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance);
+ var ctx = CreateContext(10, new[] { 1, 2, 3, 4, 5 });
var inst = CreateInstance();
inst.Stacks = 3;
@@ -164,7 +179,7 @@ namespace YachtDice.Tests
public void PostMultiplyEffect_MultipliesPostMultiplier()
{
var effect = PostMultiplyEffect.CreateForTest(2f);
- var ctx = CreateContext(10, new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance);
+ var ctx = CreateContext(10, new[] { 1, 2, 3, 4, 5 });
var inst = CreateInstance();
effect.Apply(ctx, inst).GetAwaiter().GetResult();
@@ -178,7 +193,7 @@ namespace YachtDice.Tests
public void AddCurrencyEffect_AddsToCurrencyDelta()
{
var effect = AddCurrencyEffect.CreateForTest(25);
- var ctx = CreateContext(10, new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance);
+ var ctx = CreateContext(10, new[] { 1, 2, 3, 4, 5 });
var inst = CreateInstance();
effect.Apply(ctx, inst).GetAwaiter().GetResult();
@@ -190,7 +205,7 @@ namespace YachtDice.Tests
public void AddCurrencyEffect_ScalesWithStacks()
{
var effect = AddCurrencyEffect.CreateForTest(25);
- var ctx = CreateContext(10, new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance);
+ var ctx = CreateContext(10, new[] { 1, 2, 3, 4, 5 });
var inst = CreateInstance();
inst.Stacks = 2;
@@ -205,7 +220,7 @@ namespace YachtDice.Tests
public void ConsumeChargeEffect_DecrementsRemainingUses()
{
var effect = ConsumeChargeEffect.CreateForTest(1);
- var ctx = CreateContext(10, new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance);
+ var ctx = CreateContext(10, new[] { 1, 2, 3, 4, 5 });
var def = ModifierDefinitionSO.CreateForTest("limited", null,
hasLimitedUses: true, maxUses: 3);
var inst = new ModifierInstance(def);
@@ -219,7 +234,7 @@ namespace YachtDice.Tests
public void ConsumeChargeEffect_IgnoresPermanent()
{
var effect = ConsumeChargeEffect.CreateForTest(1);
- var ctx = CreateContext(10, new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance);
+ var ctx = CreateContext(10, new[] { 1, 2, 3, 4, 5 });
var inst = CreateInstance(); // permanent (no limited uses)
effect.Apply(ctx, inst).GetAwaiter().GetResult();
@@ -232,7 +247,7 @@ namespace YachtDice.Tests
[Test]
public void FinalScore_CombinesBaseAndFlatAndMultiplier()
{
- var ctx = CreateContext(10, new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance);
+ var ctx = CreateContext(10, new[] { 1, 2, 3, 4, 5 });
ctx.FlatBonus = 5;
ctx.Multiplier = 2f;
ctx.PostMultiplier = 1.5f;
diff --git a/Assets/Scripts/Tests/Editor/ModifierPipelineTests.cs b/Assets/Scripts/Tests/Editor/ModifierPipelineTests.cs
index 4d8fb74..aff9dd4 100644
--- a/Assets/Scripts/Tests/Editor/ModifierPipelineTests.cs
+++ b/Assets/Scripts/Tests/Editor/ModifierPipelineTests.cs
@@ -1,6 +1,7 @@
using System.Collections.Generic;
using NUnit.Framework;
using UnityEngine;
+using YachtDice.Categories;
using YachtDice.Modifiers.Conditions;
using YachtDice.Modifiers.Core;
using YachtDice.Modifiers.Definition;
@@ -16,12 +17,38 @@ namespace YachtDice.Tests
private ModifierRegistry registry;
private ModifierPipeline pipeline;
+ // Тестовые категории
+ private CategoryDefinitionSO chanceCategory;
+ private CategoryDefinitionSO fullHouseCategory;
+ private CategoryDefinitionSO onesCategory;
+ private CategoryDefinitionSO threesCategory;
+ private CategoryDefinitionSO foursCategory;
+ private CategoryDefinitionSO sixesCategory;
+
[SetUp]
public void SetUp()
{
registry = new ModifierRegistry(10);
pipeline = new ModifierPipeline(registry);
- pipeline.TracingEnabled = false; // disable debug logs during tests
+ pipeline.TracingEnabled = false;
+
+ chanceCategory = SumAllCategorySO.CreateForTest("chance", "Шанс");
+ fullHouseCategory = FullHouseCategorySO.CreateForTest("full_house", "Фулл-хаус");
+ onesCategory = SumOfValueCategorySO.CreateForTest("ones", "Единицы", 1);
+ threesCategory = SumOfValueCategorySO.CreateForTest("threes", "Тройки", 3);
+ foursCategory = SumOfValueCategorySO.CreateForTest("fours", "Четвёрки", 4);
+ sixesCategory = SumOfValueCategorySO.CreateForTest("sixes", "Шестёрки", 6);
+ }
+
+ [TearDown]
+ public void TearDown()
+ {
+ Object.DestroyImmediate(chanceCategory);
+ Object.DestroyImmediate(fullHouseCategory);
+ Object.DestroyImmediate(onesCategory);
+ Object.DestroyImmediate(threesCategory);
+ Object.DestroyImmediate(foursCategory);
+ Object.DestroyImmediate(sixesCategory);
}
private ModifierDefinitionSO CreateDef(string id,
@@ -40,7 +67,7 @@ namespace YachtDice.Tests
registry.TryActivate(inst);
}
- private ModifierContext CreateScoringContext(int baseScore, int[] dice, YachtCategory category)
+ private ModifierContext CreateScoringContext(int baseScore, int[] dice, CategoryDefinitionSO category)
{
return new ModifierContext
{
@@ -64,10 +91,10 @@ namespace YachtDice.Tests
var mulDef = CreateDef("mul", TriggerType.OnCategoryScored, null,
new List { mulEffect });
- RegisterAndActivate(mulDef); // registered first, but multiplicative phase
- RegisterAndActivate(addDef); // registered second, but additive phase
+ RegisterAndActivate(mulDef);
+ RegisterAndActivate(addDef);
- var ctx = CreateScoringContext(20, new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance);
+ var ctx = CreateScoringContext(20, new[] { 1, 2, 3, 4, 5 }, chanceCategory);
var result = pipeline.Execute(TriggerType.OnCategoryScored, ctx).GetAwaiter().GetResult();
// (20 + 10) * 2 = 60
@@ -88,7 +115,7 @@ namespace YachtDice.Tests
RegisterAndActivate(postDef);
RegisterAndActivate(mulDef);
- var ctx = CreateScoringContext(10, new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance);
+ var ctx = CreateScoringContext(10, new[] { 1, 2, 3, 4, 5 }, chanceCategory);
var result = pipeline.Execute(TriggerType.OnCategoryScored, ctx).GetAwaiter().GetResult();
// (10 + 0) * 2 * 3 = 60
@@ -100,7 +127,7 @@ namespace YachtDice.Tests
[Test]
public void Execute_ConditionFails_SkipsEffect()
{
- var condition = CategoryCondition.CreateForTest(YachtCategory.FullHouse);
+ var condition = CategoryCondition.CreateForTest(fullHouseCategory);
var effect = AddFlatScoreEffect.CreateForTest(100);
var def = CreateDef("fh-bonus", TriggerType.OnCategoryScored,
@@ -110,7 +137,7 @@ namespace YachtDice.Tests
RegisterAndActivate(def);
// Scoring Ones, not FullHouse — condition should fail
- var ctx = CreateScoringContext(5, new[] { 1, 1, 1, 1, 1 }, YachtCategory.Ones);
+ var ctx = CreateScoringContext(5, new[] { 1, 1, 1, 1, 1 }, onesCategory);
var result = pipeline.Execute(TriggerType.OnCategoryScored, ctx).GetAwaiter().GetResult();
Assert.AreEqual(0, result.FlatBonus);
@@ -120,7 +147,7 @@ namespace YachtDice.Tests
[Test]
public void Execute_ConditionPasses_AppliesEffect()
{
- var condition = CategoryCondition.CreateForTest(YachtCategory.FullHouse);
+ var condition = CategoryCondition.CreateForTest(fullHouseCategory);
var effect = AddFlatScoreEffect.CreateForTest(15);
var def = CreateDef("fh-bonus", TriggerType.OnCategoryScored,
@@ -129,7 +156,7 @@ namespace YachtDice.Tests
RegisterAndActivate(def);
- var ctx = CreateScoringContext(25, new[] { 3, 3, 3, 2, 2 }, YachtCategory.FullHouse);
+ var ctx = CreateScoringContext(25, new[] { 3, 3, 3, 2, 2 }, fullHouseCategory);
var result = pipeline.Execute(TriggerType.OnCategoryScored, ctx).GetAwaiter().GetResult();
Assert.AreEqual(15, result.FlatBonus);
@@ -147,8 +174,7 @@ namespace YachtDice.Tests
RegisterAndActivate(def);
- // Fire OnCategoryScored, not OnTurnStart
- var ctx = CreateScoringContext(10, new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance);
+ var ctx = CreateScoringContext(10, new[] { 1, 2, 3, 4, 5 }, chanceCategory);
var result = pipeline.Execute(TriggerType.OnCategoryScored, ctx).GetAwaiter().GetResult();
Assert.AreEqual(0, result.FlatBonus);
@@ -180,12 +206,9 @@ namespace YachtDice.Tests
RegisterAndActivate(def1);
// dice: [3, 3, 3, 1, 2] — 3 threes
- var ctx = CreateScoringContext(9, new[] { 3, 3, 3, 1, 2 }, YachtCategory.Threes);
+ var ctx = CreateScoringContext(9, new[] { 3, 3, 3, 1, 2 }, threesCategory);
var result = pipeline.Execute(TriggerType.OnCategoryScored, ctx).GetAwaiter().GetResult();
- // Additive phase: perDieAdd (+2*3=6) + flatAdd (+10) → FlatBonus = 16
- // Multiplicative phase: perDieMul (1.5^3=3.375) then finalMul (*2) → Multiplier = 6.75
- // FinalScore = floor((9 + 16) * 6.75) = floor(168.75) = 168
Assert.AreEqual(16, result.FlatBonus);
Assert.AreEqual(168, result.FinalScore);
}
@@ -195,7 +218,7 @@ namespace YachtDice.Tests
[Test]
public void Execute_NoActiveModifiers_NoChange()
{
- var ctx = CreateScoringContext(10, new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance);
+ var ctx = CreateScoringContext(10, new[] { 1, 2, 3, 4, 5 }, chanceCategory);
var result = pipeline.Execute(TriggerType.OnCategoryScored, ctx).GetAwaiter().GetResult();
Assert.AreEqual(10, result.FinalScore);
@@ -210,10 +233,9 @@ namespace YachtDice.Tests
var def = CreateDef("inactive", TriggerType.OnCategoryScored, null,
new List { effect });
- // Add but don't activate
registry.Add(def);
- var ctx = CreateScoringContext(10, new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance);
+ var ctx = CreateScoringContext(10, new[] { 1, 2, 3, 4, 5 }, chanceCategory);
var result = pipeline.Execute(TriggerType.OnCategoryScored, ctx).GetAwaiter().GetResult();
Assert.AreEqual(0, result.FlatBonus);
@@ -233,7 +255,7 @@ namespace YachtDice.Tests
RegisterAndActivate(def);
- var ctx = CreateScoringContext(10, new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance);
+ var ctx = CreateScoringContext(10, new[] { 1, 2, 3, 4, 5 }, chanceCategory);
var result = pipeline.Execute(TriggerType.OnCategoryScored, ctx).GetAwaiter().GetResult();
Assert.AreEqual(10, result.FlatBonus);
@@ -254,7 +276,7 @@ namespace YachtDice.Tests
RegisterAndActivate(def);
- var ctx = CreateScoringContext(10, new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance);
+ var ctx = CreateScoringContext(10, new[] { 1, 2, 3, 4, 5 }, chanceCategory);
var result = pipeline.Execute(TriggerType.OnCategoryScored, ctx).GetAwaiter().GetResult();
Assert.IsNotNull(result.DebugLog);
@@ -276,13 +298,13 @@ namespace YachtDice.Tests
RegisterAndActivate(def);
// Only 2 sixes — condition requires 3
- var ctx = CreateScoringContext(12, new[] { 6, 6, 1, 2, 3 }, YachtCategory.Sixes);
+ var ctx = CreateScoringContext(12, new[] { 6, 6, 1, 2, 3 }, sixesCategory);
var result = pipeline.Execute(TriggerType.OnCategoryScored, ctx).GetAwaiter().GetResult();
Assert.AreEqual(0, result.FlatBonus);
// 3 sixes — condition passes
- var ctx2 = CreateScoringContext(18, new[] { 6, 6, 6, 1, 2 }, YachtCategory.Sixes);
+ var ctx2 = CreateScoringContext(18, new[] { 6, 6, 6, 1, 2 }, sixesCategory);
var result2 = pipeline.Execute(TriggerType.OnCategoryScored, ctx2).GetAwaiter().GetResult();
Assert.AreEqual(100, result2.FlatBonus);
@@ -303,13 +325,13 @@ namespace YachtDice.Tests
RegisterAndActivate(def);
// Below threshold
- var ctx = CreateScoringContext(15, new[] { 3, 3, 3, 3, 3 }, YachtCategory.Threes);
+ var ctx = CreateScoringContext(15, new[] { 3, 3, 3, 3, 3 }, threesCategory);
var result = pipeline.Execute(TriggerType.OnCategoryScored, ctx).GetAwaiter().GetResult();
Assert.AreEqual(1f, result.Multiplier);
// At threshold
- var ctx2 = CreateScoringContext(20, new[] { 4, 4, 4, 4, 4 }, YachtCategory.Fours);
+ var ctx2 = CreateScoringContext(20, new[] { 4, 4, 4, 4, 4 }, foursCategory);
var result2 = pipeline.Execute(TriggerType.OnCategoryScored, ctx2).GetAwaiter().GetResult();
Assert.AreEqual(2f, result2.Multiplier);
@@ -320,7 +342,7 @@ namespace YachtDice.Tests
[Test]
public void ToScoreResult_ConvertsCorrectly()
{
- var ctx = CreateScoringContext(10, new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance);
+ var ctx = CreateScoringContext(10, new[] { 1, 2, 3, 4, 5 }, chanceCategory);
ctx.FlatBonus = 5;
ctx.Multiplier = 2f;
ctx.PostMultiplier = 1.5f;
@@ -330,7 +352,7 @@ namespace YachtDice.Tests
Assert.AreEqual(10, sr.BaseScore);
Assert.AreEqual(5, sr.FlatBonus);
Assert.AreEqual(3f, sr.Multiplier, 0.001f); // 2 * 1.5
- Assert.AreEqual(YachtCategory.Chance, sr.Category);
+ Assert.AreEqual(chanceCategory, sr.Category);
}
}
}
diff --git a/Assets/Scripts/Tests/Editor/ScoringSystemTests.cs b/Assets/Scripts/Tests/Editor/ScoringSystemTests.cs
index 661716a..55cdf8b 100644
--- a/Assets/Scripts/Tests/Editor/ScoringSystemTests.cs
+++ b/Assets/Scripts/Tests/Editor/ScoringSystemTests.cs
@@ -1,15 +1,35 @@
+using System.Collections.Generic;
using NUnit.Framework;
using UnityEngine;
+using YachtDice.Categories;
+using YachtDice.Dice;
using YachtDice.Scoring;
namespace YachtDice.Tests
{
public class ScoringSystemTests
{
- private ScoringSystem CreateScoringSystem()
+ private CategoryDefinitionSO yachtCategory;
+ private CategoryDefinitionSO onesCategory;
+ private CategoryDefinitionSO twosCategory;
+ private CategoryDefinitionSO chanceCategory;
+ private CategoryCatalogSO catalog;
+ private DieDefinitionSO standardDie;
+
+ [SetUp]
+ public void SetUp()
{
- var go = new GameObject("ScoringSystem");
- return go.AddComponent();
+ standardDie = DieDefinitionSO.CreateForTest("d6", "d6");
+
+ yachtCategory = NOfAKindCategorySO.CreateForTest("yacht", "Яхта", 5, fixedScoreMode: true, score: 50);
+ onesCategory = SumOfValueCategorySO.CreateForTest("ones", "Единицы", 1);
+ twosCategory = SumOfValueCategorySO.CreateForTest("twos", "Двойки", 2);
+ chanceCategory = SumAllCategorySO.CreateForTest("chance", "Шанс");
+
+ catalog = CategoryCatalogSO.CreateForTest(new List
+ {
+ onesCategory, twosCategory, yachtCategory, chanceCategory
+ });
}
[TearDown]
@@ -17,13 +37,35 @@ namespace YachtDice.Tests
{
foreach (var go in Object.FindObjectsByType(FindObjectsSortMode.None))
Object.DestroyImmediate(go.gameObject);
+
+ Object.DestroyImmediate(yachtCategory);
+ Object.DestroyImmediate(onesCategory);
+ Object.DestroyImmediate(twosCategory);
+ Object.DestroyImmediate(chanceCategory);
+ Object.DestroyImmediate(catalog);
+ Object.DestroyImmediate(standardDie);
+ }
+
+ private ScoringSystem CreateScoringSystem()
+ {
+ var go = new GameObject("ScoringSystem");
+ return go.AddComponent();
+ }
+
+ private IReadOnlyList CreateDice(params int[] values)
+ {
+ var dice = new DieInstance[values.Length];
+ for (int i = 0; i < values.Length; i++)
+ dice[i] = new DieInstance(standardDie, values[i]);
+ return dice;
}
[Test]
public void ScoreCategory_WithNoModifiers_CalculatesBaseOnly()
{
var system = CreateScoringSystem();
- var result = system.ScoreCategory(new[] { 6, 6, 6, 6, 6 }, YachtCategory.Yacht);
+ var dice = CreateDice(6, 6, 6, 6, 6);
+ var result = system.ScoreCategory(dice, yachtCategory);
Assert.AreEqual(50, result.BaseScore);
Assert.AreEqual(0, result.FlatBonus);
@@ -35,7 +77,7 @@ namespace YachtDice.Tests
public void ScoreCategory_FiresOnCategoryConfirmed()
{
var system = CreateScoringSystem();
- YachtCategory firedCategory = (YachtCategory)(-1);
+ CategoryDefinitionSO firedCategory = null;
ScoreResult firedResult = default;
system.OnCategoryConfirmed += (cat, res) =>
@@ -44,9 +86,10 @@ namespace YachtDice.Tests
firedResult = res;
};
- system.ScoreCategory(new[] { 1, 1, 1, 1, 1 }, YachtCategory.Ones);
+ var dice = CreateDice(1, 1, 1, 1, 1);
+ system.ScoreCategory(dice, onesCategory);
- Assert.AreEqual(YachtCategory.Ones, firedCategory);
+ Assert.AreEqual(onesCategory, firedCategory);
Assert.AreEqual(5, firedResult.BaseScore);
}
@@ -54,17 +97,19 @@ namespace YachtDice.Tests
public void ScoreCategory_PreventsDuplicateCategory()
{
var system = CreateScoringSystem();
- system.ScoreCategory(new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance);
+ var dice = CreateDice(1, 2, 3, 4, 5);
+ system.ScoreCategory(dice, chanceCategory);
Assert.Throws(() =>
- system.ScoreCategory(new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance));
+ system.ScoreCategory(dice, chanceCategory));
}
[Test]
public void PreviewScore_WithNoModifiers_CalculatesBaseOnly()
{
var system = CreateScoringSystem();
- var result = system.PreviewScore(new[] { 1, 2, 3, 4, 5 }, YachtCategory.Chance);
+ var dice = CreateDice(1, 2, 3, 4, 5);
+ var result = system.PreviewScore(dice, chanceCategory);
Assert.AreEqual(15, result.BaseScore);
Assert.AreEqual(0, result.FlatBonus);
@@ -75,8 +120,8 @@ namespace YachtDice.Tests
public void TotalScore_SumsAllScoredCategories()
{
var system = CreateScoringSystem();
- system.ScoreCategory(new[] { 1, 1, 1, 1, 1 }, YachtCategory.Ones);
- system.ScoreCategory(new[] { 2, 2, 2, 2, 2 }, YachtCategory.Twos);
+ system.ScoreCategory(CreateDice(1, 1, 1, 1, 1), onesCategory);
+ system.ScoreCategory(CreateDice(2, 2, 2, 2, 2), twosCategory);
Assert.AreEqual(15, system.TotalScore); // 5 + 10
}
@@ -85,12 +130,84 @@ namespace YachtDice.Tests
public void ResetScorecard_ClearsAll()
{
var system = CreateScoringSystem();
- system.ScoreCategory(new[] { 1, 1, 1, 1, 1 }, YachtCategory.Ones);
+ system.ScoreCategory(CreateDice(1, 1, 1, 1, 1), onesCategory);
system.ResetScorecard();
Assert.AreEqual(0, system.TotalScore);
- Assert.IsFalse(system.IsCategoryUsed(YachtCategory.Ones));
+ Assert.IsFalse(system.IsCategoryUsed(onesCategory));
+ }
+
+ // ── Category SO Unit Tests ──────────────────────────────────
+
+ [Test]
+ public void SumOfValueCategory_SumsCorrectly()
+ {
+ var dice = CreateDice(3, 3, 3, 1, 2);
+ var cat = SumOfValueCategorySO.CreateForTest("threes", "Тройки", 3);
+
+ Assert.AreEqual(9, cat.Calculate(dice));
+
+ Object.DestroyImmediate(cat);
+ }
+
+ [Test]
+ public void NOfAKindCategory_ThreeOfAKind_ReturnsSumOrZero()
+ {
+ var cat = NOfAKindCategorySO.CreateForTest("three_of_a_kind", "Тройка", 3);
+
+ Assert.AreEqual(17, cat.Calculate(CreateDice(4, 4, 4, 3, 2))); // sum = 17
+ Assert.AreEqual(0, cat.Calculate(CreateDice(1, 2, 3, 4, 5))); // no 3-of-a-kind
+
+ Object.DestroyImmediate(cat);
+ }
+
+ [Test]
+ public void NOfAKindCategory_Yacht_ReturnsFixedScore()
+ {
+ Assert.AreEqual(50, yachtCategory.Calculate(CreateDice(6, 6, 6, 6, 6)));
+ Assert.AreEqual(0, yachtCategory.Calculate(CreateDice(6, 6, 6, 6, 1)));
+ }
+
+ [Test]
+ public void FullHouseCategory_CalculatesCorrectly()
+ {
+ var cat = FullHouseCategorySO.CreateForTest("fh", "Фулл-хаус", 25);
+
+ Assert.AreEqual(25, cat.Calculate(CreateDice(3, 3, 3, 2, 2)));
+ Assert.AreEqual(0, cat.Calculate(CreateDice(3, 3, 3, 3, 2)));
+
+ Object.DestroyImmediate(cat);
+ }
+
+ [Test]
+ public void StraightCategory_SmallStraight()
+ {
+ var cat = StraightCategorySO.CreateForTest("ss", "Малый стрит", 4, 30);
+
+ Assert.AreEqual(30, cat.Calculate(CreateDice(1, 2, 3, 4, 6)));
+ Assert.AreEqual(0, cat.Calculate(CreateDice(1, 2, 3, 5, 6)));
+
+ Object.DestroyImmediate(cat);
+ }
+
+ [Test]
+ public void StraightCategory_LargeStraight()
+ {
+ var cat = StraightCategorySO.CreateForTest("ls", "Большой стрит", 5, 40);
+
+ Assert.AreEqual(40, cat.Calculate(CreateDice(1, 2, 3, 4, 5)));
+ Assert.AreEqual(40, cat.Calculate(CreateDice(2, 3, 4, 5, 6)));
+ Assert.AreEqual(0, cat.Calculate(CreateDice(1, 2, 3, 4, 6)));
+
+ Object.DestroyImmediate(cat);
+ }
+
+ [Test]
+ public void SumAllCategory_SumsEverything()
+ {
+ Assert.AreEqual(15, chanceCategory.Calculate(CreateDice(1, 2, 3, 4, 5)));
+ Assert.AreEqual(30, chanceCategory.Calculate(CreateDice(6, 6, 6, 6, 6)));
}
}
}
diff --git a/Assets/Scripts/UI/CategoryRowView.cs b/Assets/Scripts/UI/CategoryRowView.cs
index f2de289..8f3d6f7 100644
--- a/Assets/Scripts/UI/CategoryRowView.cs
+++ b/Assets/Scripts/UI/CategoryRowView.cs
@@ -2,7 +2,7 @@ using System;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
-using YachtDice.Scoring;
+using YachtDice.Categories;
namespace YachtDice.UI
{
@@ -21,16 +21,16 @@ namespace YachtDice.UI
[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 CategoryDefinitionSO category;
private bool isUsed;
- public event Action OnCategorySelected;
+ public event Action OnCategorySelected;
- public void Initialize(YachtCategory cat, string displayName)
+ public void Initialize(CategoryDefinitionSO categoryDef)
{
- category = cat;
+ category = categoryDef;
isUsed = false;
- categoryNameText.text = displayName;
+ categoryNameText.text = categoryDef.DisplayName;
previewText.text = "";
recordedScoreText.text = "-";
selectButton.onClick.AddListener(HandleClick);
diff --git a/Assets/Scripts/UI/GameController.cs b/Assets/Scripts/UI/GameController.cs
index 1911c37..8b57dcb 100644
--- a/Assets/Scripts/UI/GameController.cs
+++ b/Assets/Scripts/UI/GameController.cs
@@ -1,7 +1,8 @@
-using System;
using System.Collections.Generic;
using UnityEngine;
using VContainer;
+using YachtDice.Categories;
+using YachtDice.Dice;
using YachtDice.Game;
using YachtDice.Scoring;
using YachtDice.Economy;
@@ -34,33 +35,25 @@ namespace YachtDice.UI
[SerializeField] private int maxRollsPerTurn = 3;
[SerializeField] private int maxActiveModifierSlots = 5;
- private static readonly YachtCategory[] UpperCategories =
- {
- YachtCategory.Ones, YachtCategory.Twos, YachtCategory.Threes,
- YachtCategory.Fours, YachtCategory.Fives, YachtCategory.Sixes
- };
-
private const int UpperBonusThreshold = 63;
private const int UpperBonusValue = 35;
- private int totalCategoryCount;
-
private ModifierRegistry modifierRegistry;
+ private CategoryCatalogSO categoryCatalog;
private InventoryModel inventoryModel;
private ShopModel shopModel;
[Inject]
- public void Construct(ModifierRegistry modifierRegistry)
+ public void Construct(ModifierRegistry modifierRegistry, CategoryCatalogSO categoryCatalog)
{
this.modifierRegistry = modifierRegistry;
+ this.categoryCatalog = categoryCatalog;
}
// ── Lifecycle ──────────────────────────────────────────────
private void Awake()
{
- totalCategoryCount = Enum.GetValues(typeof(YachtCategory)).Length;
-
// Model → Controller
gameManager.OnTurnStarted += HandleTurnStarted;
gameManager.OnRollComplete += HandleRollComplete;
@@ -83,6 +76,9 @@ namespace YachtDice.UI
private void Start()
{
+ // Инициализируем скоркарту из каталога категорий
+ scoreCardView.Initialize(categoryCatalog);
+
InitializeModifierSystems();
}
@@ -197,6 +193,7 @@ namespace YachtDice.UI
private void HandleTurnStarted(int turn)
{
+ int totalCategoryCount = categoryCatalog.Count;
gameInfoView.SetTurnText(turn, totalCategoryCount);
dicePanelView.ResetForNewTurn();
dicePanelView.SetRollButtonState(true, 0, maxRollsPerTurn);
@@ -212,7 +209,7 @@ namespace YachtDice.UI
int[] values = diceManager.GetCurrentValues();
dicePanelView.SetAllDiceValues(values);
- UpdatePreviewScores(values);
+ UpdatePreviewScores();
}
private void HandleDieSettled(int index, int value)
@@ -220,7 +217,7 @@ namespace YachtDice.UI
dicePanelView.SetDieValue(index, value);
}
- private void HandleScored(YachtCategory category, int finalScore)
+ private void HandleScored(CategoryDefinitionSO category, int finalScore)
{
scoreCardView.SetCategoryScored(category, finalScore);
UpdateTotalDisplay();
@@ -262,7 +259,7 @@ namespace YachtDice.UI
dicePanelView.SetDieLocked(index, isLocked);
}
- private void HandleCategorySelected(YachtCategory category)
+ private void HandleCategorySelected(CategoryDefinitionSO category)
{
if (!gameManager.CanScore) return;
if (scoringSystem.IsCategoryUsed(category)) return;
@@ -317,17 +314,19 @@ namespace YachtDice.UI
// ── Helpers ────────────────────────────────────────────────
- private void UpdatePreviewScores(int[] diceValues)
+ private void UpdatePreviewScores()
{
- var previews = new Dictionary();
- var categories = (YachtCategory[])Enum.GetValues(typeof(YachtCategory));
+ var dice = diceManager.GetDice();
+ var previews = new Dictionary();
+ var allCategories = categoryCatalog.All;
- for (int i = 0; i < categories.Length; i++)
+ for (int i = 0; i < allCategories.Count; i++)
{
- if (scoringSystem.IsCategoryUsed(categories[i])) continue;
+ var cat = allCategories[i];
+ if (scoringSystem.IsCategoryUsed(cat)) continue;
- ScoreResult result = scoringSystem.PreviewScore(diceValues, categories[i]);
- previews[categories[i]] = result.FinalScore;
+ ScoreResult result = scoringSystem.PreviewScore(dice, cat);
+ previews[cat] = result.FinalScore;
}
scoreCardView.UpdatePreviews(previews);
@@ -335,29 +334,33 @@ namespace YachtDice.UI
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;
- }
-
+ int upperSum = CalculateUpperSum();
bool hasBonus = upperSum >= UpperBonusThreshold;
int displayTotal = CalculateDisplayTotal();
scoreCardView.UpdateTotalDisplay(displayTotal, upperSum, hasBonus);
}
+ private int CalculateUpperSum()
+ {
+ int upperSum = 0;
+ var allCategories = categoryCatalog.All;
+
+ for (int i = 0; i < allCategories.Count; i++)
+ {
+ if (!allCategories[i].IsUpperSection) continue;
+
+ int catScore = scoringSystem.GetCategoryScore(allCategories[i]);
+ if (catScore >= 0) upperSum += catScore;
+ }
+
+ return upperSum;
+ }
+
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;
- }
+ int upperSum = CalculateUpperSum();
if (upperSum >= UpperBonusThreshold)
total += UpperBonusValue;
diff --git a/Assets/Scripts/UI/ScoreCardView.cs b/Assets/Scripts/UI/ScoreCardView.cs
index d089304..fbfb584 100644
--- a/Assets/Scripts/UI/ScoreCardView.cs
+++ b/Assets/Scripts/UI/ScoreCardView.cs
@@ -2,13 +2,13 @@ using System;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
-using YachtDice.Scoring;
+using YachtDice.Categories;
namespace YachtDice.UI
{
public class ScoreCardView : MonoBehaviour
{
- [Header("Category Rows (in YachtCategory enum order)")]
+ [Header("Category Rows (порядок соответствует каталогу)")]
[SerializeField] private List categoryRows = new();
[Header("Summary")]
@@ -16,48 +16,41 @@ namespace YachtDice.UI
[SerializeField] private TMP_Text upperBonusText;
[SerializeField] private TMP_Text totalScoreText;
- public event Action OnCategorySelected;
+ public event Action OnCategorySelected;
- private static readonly string[] CategoryNames =
+ private CategoryCatalogSO catalog;
+ private Dictionary categoryToRowIndex;
+
+ ///
+ /// Инициализирует скоркарту из каталога категорий.
+ /// Вызывается из GameController после DI.
+ ///
+ public void Initialize(CategoryCatalogSO categoryCatalog)
{
- "Единицы",
- "Двойки",
- "Тройки",
- "Четвёрки",
- "Пятёрки",
- "Шестёрки",
- "Тройка",
- "Каре",
- "Фулл-хаус",
- "Малый стрит",
- "Большой стрит",
- "Яхта",
- "Шанс"
- };
+ catalog = categoryCatalog;
+ categoryToRowIndex = new Dictionary();
- private YachtCategory[] _allCategories;
+ var all = catalog.All;
+ int count = Mathf.Min(categoryRows.Count, all.Count);
- private void Awake()
- {
- _allCategories = (YachtCategory[])Enum.GetValues(typeof(YachtCategory));
-
- for (int i = 0; i < categoryRows.Count && i < _allCategories.Length; i++)
+ for (int i = 0; i < count; i++)
{
- categoryRows[i].Initialize(_allCategories[i], CategoryNames[i]);
+ categoryRows[i].Initialize(all[i]);
categoryRows[i].OnCategorySelected += HandleCategorySelected;
+ categoryToRowIndex[all[i]] = i;
}
UpdateTotalDisplay(0, 0, false);
}
- public void UpdatePreviews(Dictionary previews)
+ public void UpdatePreviews(Dictionary previews)
{
- for (int i = 0; i < categoryRows.Count && i < _allCategories.Length; i++)
+ foreach (var kvp in previews)
{
- if (previews.TryGetValue(_allCategories[i], out int preview))
+ if (categoryToRowIndex.TryGetValue(kvp.Key, out int rowIndex))
{
- categoryRows[i].ShowPreview(preview);
- categoryRows[i].SetInteractable(true);
+ categoryRows[rowIndex].ShowPreview(kvp.Value);
+ categoryRows[rowIndex].SetInteractable(true);
}
}
}
@@ -71,10 +64,9 @@ namespace YachtDice.UI
}
}
- public void SetCategoryScored(YachtCategory category, int score)
+ public void SetCategoryScored(CategoryDefinitionSO category, int score)
{
- int index = (int)category;
- if (index >= 0 && index < categoryRows.Count)
+ if (categoryToRowIndex.TryGetValue(category, out int index))
categoryRows[index].SetRecordedScore(score);
}
@@ -99,7 +91,7 @@ namespace YachtDice.UI
UpdateTotalDisplay(0, 0, false);
}
- private void HandleCategorySelected(YachtCategory category)
+ private void HandleCategorySelected(CategoryDefinitionSO category)
{
OnCategorySelected?.Invoke(category);
}