[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
@@ -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;