Files
2026-03-28 12:53:34 +07:00

274 lines
10 KiB
C#

using System.Collections.Generic;
using NUnit.Framework;
using UnityEngine;
using YachtDice.Categories;
using YachtDice.Dice;
using YachtDice.Economy;
using YachtDice.Run;
using YachtDice.Scoring;
namespace YachtDice.Tests
{
public sealed class RunLoopServiceTests
{
private readonly List<Object> _createdAssets = new();
private readonly IReadOnlyList<IDice> _emptyDice = new IDice[0];
private CurrencyBank _currencyBank;
private ScoringSystem _scoringSystem;
private StoredRollBank _storedRollBank;
private RunBalanceConfigSO _config;
[SetUp]
public void SetUp()
{
var bankGo = new GameObject("CurrencyBank");
_currencyBank = bankGo.AddComponent<CurrencyBank>();
_currencyBank.SetBalance(0);
var scoringGo = new GameObject("ScoringSystem");
_scoringSystem = scoringGo.AddComponent<ScoringSystem>();
_storedRollBank = new StoredRollBank();
_config = RunBalanceConfigSO.CreateDefault();
_createdAssets.Add(_config);
}
[TearDown]
public void TearDown()
{
foreach (var scoring in Object.FindObjectsByType<ScoringSystem>(FindObjectsSortMode.None))
Object.DestroyImmediate(scoring.gameObject);
foreach (var bank in Object.FindObjectsByType<CurrencyBank>(FindObjectsSortMode.None))
Object.DestroyImmediate(bank.gameObject);
for (var i = 0; i < _createdAssets.Count; i++)
{
if (_createdAssets[i] != null)
Object.DestroyImmediate(_createdAssets[i]);
}
_createdAssets.Clear();
}
[Test]
public void StartNewRun_StartsFirstStageWithoutShop()
{
var categories = CreateFixedCatalog(100, 100, 100);
var service = CreateService(categories);
service.StartNewRun();
Assert.AreEqual(30, service.State.BaseQuota);
Assert.AreEqual(1, service.State.BetIndex);
Assert.AreEqual(RunPhase.StageStart, service.State.Phase);
Assert.AreEqual(0, service.State.StoredRolls);
Assert.AreEqual(0, _currencyBank.Balance);
Assert.IsFalse(service.State.IsShopAvailable);
}
[Test]
public void ClearedStage_AwardsCurrencyAndBanksUnusedRolls()
{
var categories = CreateFixedCatalog(100, 100, 100);
var service = CreateService(categories);
service.StartNewRun();
service.TryBeginRoll();
service.NotifyRollResolved(_emptyDice);
var cleared = service.TryScoreCategoryAsync(_emptyDice, categories.All[0]).GetAwaiter().GetResult();
Assert.IsTrue(cleared);
Assert.AreEqual(25, _currencyBank.Balance);
Assert.AreEqual(2, service.State.StoredRolls);
Assert.AreEqual(2, service.State.StageNumber);
Assert.AreEqual(RunPhase.StageStart, service.State.Phase);
Assert.IsTrue(service.State.IsShopAvailable);
}
[Test]
public void CategoryCanBeScored_WithZeroResultAfterFirstRoll()
{
var categories = CreateFixedCatalog(0, 100, 100);
var service = CreateService(categories);
service.StartNewRun();
service.TryBeginRoll();
service.NotifyRollResolved(_emptyDice);
Assert.IsTrue(service.CanScoreCategory(_emptyDice, categories.All[0]));
Assert.IsTrue(service.TryScoreCategoryAsync(_emptyDice, categories.All[0]).GetAwaiter().GetResult());
Assert.AreEqual(30, service.State.CurrentStageTarget);
Assert.AreEqual(1, service.State.StageNumber);
Assert.AreEqual(RunPhase.StageStart, service.State.Phase);
}
[Test]
public void TargetDecreasesByRecordedScore_AndStageContinues()
{
var categories = CreateFixedCatalog(5, 100, 100);
var service = CreateService(categories);
service.StartNewRun();
service.TryBeginRoll();
service.NotifyRollResolved(_emptyDice);
Assert.IsTrue(service.TryScoreCategoryAsync(_emptyDice, categories.All[0]).GetAwaiter().GetResult());
Assert.AreEqual(25, service.State.CurrentStageTarget);
Assert.AreEqual(1, service.State.StageNumber);
Assert.AreEqual(RunPhase.StageStart, service.State.Phase);
}
[Test]
public void StoredRolls_CanBeConsumedOnLaterStage()
{
var categories = CreateFixedCatalog(100, 100, 100);
var service = CreateService(categories);
service.StartNewRun();
service.TryBeginRoll();
service.NotifyRollResolved(_emptyDice);
service.TryScoreCategoryAsync(_emptyDice, categories.All[0]).GetAwaiter().GetResult();
for (var i = 0; i < 4; i++)
{
Assert.IsTrue(service.TryBeginRoll());
service.NotifyRollResolved(_emptyDice);
}
service.TryScoreCategoryAsync(_emptyDice, categories.All[1]).GetAwaiter().GetResult();
Assert.AreEqual(1, service.State.StoredRolls);
Assert.AreEqual(50, _currencyBank.Balance);
}
[Test]
public void CompletedCycle_GrantsBonusRaisesQuotaAndStartsNextBet()
{
var categories = CreateFixedCatalog(100, 100, 100);
var service = CreateService(categories);
service.StartNewRun();
ClearStageInOneRoll(service, categories.All[0]);
ClearStageInOneRoll(service, categories.All[1]);
ClearStageInOneRoll(service, categories.All[2]);
Assert.AreEqual(135, _currencyBank.Balance);
Assert.AreEqual(60, service.State.BaseQuota);
Assert.AreEqual(2, service.State.BetIndex);
Assert.AreEqual(RunPhase.StageStart, service.State.Phase);
Assert.AreEqual(6, service.State.StoredRolls);
Assert.AreEqual(0, _scoringSystem.CategoriesFilledCount);
Assert.IsTrue(service.State.IsShopAvailable);
}
[Test]
public void FailedStage_EndsRunWhenNoTargetCanBeMet()
{
var categories = CreateFixedCatalog(10);
var service = CreateService(categories);
service.StartNewRun();
Assert.IsTrue(service.TryBeginRoll());
service.NotifyRollResolved(_emptyDice);
Assert.IsFalse(service.State.IsFailed);
Assert.AreEqual(RunPhase.CategorySelection, service.State.Phase);
}
[Test]
public void ShopUnlocksAfterFirstClear_AndClosesAfterFirstRoll()
{
var categories = CreateFixedCatalog(100, 100, 100, 100);
var service = CreateService(categories);
service.StartNewRun();
Assert.IsFalse(service.State.IsShopAvailable);
ClearStageInOneRoll(service, categories.All[0]);
Assert.IsTrue(service.State.IsShopAvailable);
Assert.AreEqual(RunPhase.StageStart, service.State.Phase);
Assert.IsTrue(service.TryBeginRoll());
Assert.IsFalse(service.State.IsShopAvailable);
service.NotifyRollResolved(_emptyDice);
Assert.IsTrue(service.TryScoreCategoryAsync(_emptyDice, categories.All[1]).GetAwaiter().GetResult());
Assert.IsTrue(service.State.IsShopAvailable);
ClearStageInOneRoll(service, categories.All[2]);
Assert.AreEqual(RunPhase.StageStart, service.State.Phase);
Assert.AreEqual(2, service.State.BetIndex);
Assert.IsTrue(service.State.IsShopAvailable);
}
[Test]
public void CanBeginRoll_BecomesFalse_WhenNoRollsOrStoredRollsRemain()
{
var categories = CreateFixedCatalog(100, 100, 100, 100);
var service = CreateService(categories);
service.StartNewRun();
Assert.IsTrue(service.TryBeginRoll());
service.NotifyRollResolved(_emptyDice);
Assert.IsTrue(service.TryBeginRoll());
service.NotifyRollResolved(_emptyDice);
Assert.IsTrue(service.TryBeginRoll());
service.NotifyRollResolved(_emptyDice);
Assert.IsFalse(service.CanBeginRoll());
Assert.AreEqual(RunPhase.CategorySelection, service.State.Phase);
}
private RunLoopService CreateService(CategoryCatalog catalog)
{
_scoringSystem.Construct(null, null, catalog, _currencyBank);
return new RunLoopService(_config, _scoringSystem, _currencyBank, _storedRollBank);
}
private CategoryCatalog CreateFixedCatalog(params int[] scores)
{
var categories = new List<CategoryDefinition>();
for (var i = 0; i < scores.Length; i++)
{
var category = FixedScoreCategory.Create($"cat_{i}", scores[i]);
_createdAssets.Add(category);
categories.Add(category);
}
var catalog = CategoryCatalog.CreateForTest(categories);
_createdAssets.Add(catalog);
return catalog;
}
private void ClearStageInOneRoll(RunLoopService service, CategoryDefinition category)
{
Assert.IsTrue(service.TryBeginRoll());
service.NotifyRollResolved(_emptyDice);
Assert.IsTrue(service.TryScoreCategoryAsync(_emptyDice, category).GetAwaiter().GetResult());
}
private sealed class FixedScoreCategory : CategoryDefinition
{
[SerializeField] private int fixedScore;
public override int Calculate(IReadOnlyList<IDice> dice)
{
return fixedScore;
}
public static FixedScoreCategory Create(string id, int score)
{
var category = CreateInstance<FixedScoreCategory>();
category.SetTestData(id, id);
category.fixedScore = score;
return category;
}
}
}
}