[Refactor] Replace hardcoded categories with data-driven SO system and abstract dice

- Add abstract dice system (IDie interface, DieDefinitionSO, StandardDieSO, DieInstance)
  to support future custom dice types while keeping backward compat via int[] DiceValues
- Replace YachtCategory enum and CategoryScorer switch with CategoryDefinitionSO hierarchy:
  SumOfValueCategorySO, NOfAKindCategorySO, FullHouseCategorySO, StraightCategorySO, SumAllCategorySO
- Add CategoryCatalogSO for ordered category collections and DiceCheckUtility for shared logic
- Refactor ScoringSystem, Views, GameManager, GameController to use SO references
- Update CategoryCondition modifier to use SO reference instead of enum
- Update all editor tests to use SO-based categories and DieInstance

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-01 11:46:50 +07:00
parent 6a48d68f75
commit 0f9b162061
31 changed files with 845 additions and 298 deletions
+25 -15
View File
@@ -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();
}
/// <summary>Возвращает абстрактный список дайсов (основной API).</summary>
public IReadOnlyList<IDie> GetDice() => diceInstances;
/// <summary>Возвращает копию текущих значений (обратная совместимость).</summary>
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<Dice.Dice>();
if (diceComponent != null && diceComponent.TryGetTopValue(out int val))
currentValues[i] = val;
diceInstances[i].Value = val;
}
}
}
+6 -5
View File
@@ -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<int> OnTurnStarted;
public event Action<int> OnRollComplete;
public event Action<YachtCategory, int> OnScored;
public event Action<CategoryDefinitionSO, int> OnScored;
public event Action<int> 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}");