132 lines
5.2 KiB
C#
132 lines
5.2 KiB
C#
using SVSim.Database.Enums;
|
|
using SVSim.Database.Models;
|
|
using SVSim.EmulatedEntrypoint.Services;
|
|
|
|
namespace SVSim.UnitTests.Services;
|
|
|
|
public class PackOpenServiceTests
|
|
{
|
|
/// <summary>Deterministic RNG that returns the supplied doubles in order, cycling.</summary>
|
|
private sealed class ScriptedRandom : IRandom
|
|
{
|
|
private readonly double[] _seq; private int _i;
|
|
public ScriptedRandom(params double[] seq) { _seq = seq; }
|
|
public double NextDouble() { var v = _seq[_i++ % _seq.Length]; return v; }
|
|
public int Next(int maxExclusive) => (int)(NextDouble() * maxExclusive);
|
|
}
|
|
|
|
/// <summary>Simple in-memory pool keyed by rarity for slot-distribution tests.</summary>
|
|
private sealed class StubPool : ICardPoolProvider
|
|
{
|
|
private readonly IReadOnlyList<ShadowverseCardEntry> _cards;
|
|
public StubPool(IReadOnlyList<ShadowverseCardEntry> cards) { _cards = cards; }
|
|
public IReadOnlyList<ShadowverseCardEntry> GetPool(PackConfigEntry _) => _cards;
|
|
}
|
|
|
|
private static List<ShadowverseCardEntry> MakeFourCards() => new()
|
|
{
|
|
new ShadowverseCardEntry { Id = 1, Rarity = Rarity.Bronze },
|
|
new ShadowverseCardEntry { Id = 2, Rarity = Rarity.Silver },
|
|
new ShadowverseCardEntry { Id = 3, Rarity = Rarity.Gold },
|
|
new ShadowverseCardEntry { Id = 4, Rarity = Rarity.Legendary },
|
|
};
|
|
|
|
private static PackConfigEntry StandardPack() => new()
|
|
{
|
|
Id = 10001, BasePackId = 10001, PackCategory = PackCategory.None,
|
|
};
|
|
|
|
[Test]
|
|
public void Draw_returns_eight_cards_for_one_pack()
|
|
{
|
|
var svc = new PackOpenService();
|
|
var pool = new StubPool(MakeFourCards());
|
|
var rng = new ScriptedRandom(0.5); // anything in Bronze/Silver range for non-slot-8
|
|
|
|
var result = svc.Draw(StandardPack(), pool, packNumber: 1, excludeCardIds: Array.Empty<long>(), rng: rng);
|
|
|
|
Assert.That(result.Cards.Count, Is.EqualTo(8));
|
|
}
|
|
|
|
[Test]
|
|
public void Draw_slot_8_never_returns_bronze_for_standard_pack()
|
|
{
|
|
var svc = new PackOpenService();
|
|
var pool = new StubPool(MakeFourCards());
|
|
|
|
for (int trial = 0; trial < 1000; trial++)
|
|
{
|
|
var result = svc.Draw(StandardPack(), pool, 1, Array.Empty<long>(), new SystemRandom(trial));
|
|
Assert.That(result.Cards[7].Rarity, Is.Not.EqualTo(Rarity.Bronze),
|
|
$"slot 8 must never be Bronze (trial {trial})");
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void Draw_legendary_special_forces_slot_8_to_legendary()
|
|
{
|
|
var svc = new PackOpenService();
|
|
var pool = new StubPool(MakeFourCards());
|
|
var pack = new PackConfigEntry { Id = 92001, BasePackId = 90001, PackCategory = PackCategory.SpecialCardPack };
|
|
|
|
for (int trial = 0; trial < 100; trial++)
|
|
{
|
|
var result = svc.Draw(pack, pool, 1, Array.Empty<long>(), new SystemRandom(trial));
|
|
Assert.That(result.Cards[7].Rarity, Is.EqualTo(Rarity.Legendary),
|
|
$"legendary-special pack slot 8 must be Legendary (trial {trial})");
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void Draw_distribution_over_10k_slots_1_to_7_matches_declared_rates_within_2_percent()
|
|
{
|
|
var svc = new PackOpenService();
|
|
var pool = new StubPool(MakeFourCards());
|
|
var counts = new Dictionary<Rarity, int>
|
|
{
|
|
{ Rarity.Bronze, 0 }, { Rarity.Silver, 0 }, { Rarity.Gold, 0 }, { Rarity.Legendary, 0 }
|
|
};
|
|
|
|
var rng = new SystemRandom(seed: 42);
|
|
const int packs = 10_000;
|
|
for (int i = 0; i < packs; i++)
|
|
{
|
|
var r = svc.Draw(StandardPack(), pool, 1, Array.Empty<long>(), rng);
|
|
// Only look at slots 0..6 (the unrestricted rarity slots)
|
|
for (int s = 0; s < 7; s++) counts[r.Cards[s].Rarity]++;
|
|
}
|
|
|
|
int total = packs * 7;
|
|
double bronze = counts[Rarity.Bronze] / (double)total;
|
|
double silver = counts[Rarity.Silver] / (double)total;
|
|
double gold = counts[Rarity.Gold] / (double)total;
|
|
double leg = counts[Rarity.Legendary] / (double)total;
|
|
|
|
Assert.That(bronze, Is.EqualTo(0.6744).Within(0.02), $"bronze rate {bronze:P}");
|
|
Assert.That(silver, Is.EqualTo(0.2500).Within(0.02), $"silver rate {silver:P}");
|
|
Assert.That(gold, Is.EqualTo(0.0600).Within(0.01), $"gold rate {gold:P}");
|
|
Assert.That(leg, Is.EqualTo(0.0150).Within(0.01), $"legendary rate {leg:P}");
|
|
}
|
|
|
|
[Test]
|
|
public void Draw_excludes_listed_card_ids()
|
|
{
|
|
var svc = new PackOpenService();
|
|
// Pool with two bronze cards; exclude one — every Bronze slot must pick the other.
|
|
var pool = new StubPool(new List<ShadowverseCardEntry>
|
|
{
|
|
new() { Id = 1, Rarity = Rarity.Bronze },
|
|
new() { Id = 99, Rarity = Rarity.Bronze },
|
|
new() { Id = 2, Rarity = Rarity.Silver },
|
|
});
|
|
|
|
var rng = new SystemRandom(seed: 7);
|
|
var result = svc.Draw(StandardPack(), pool, 1, excludeCardIds: new long[] { 1 }, rng: rng);
|
|
|
|
foreach (var c in result.Cards.Where(x => x.Rarity == Rarity.Bronze))
|
|
{
|
|
Assert.That(c.CardId, Is.EqualTo(99), "excluded card 1 must never appear in Bronze slot");
|
|
}
|
|
}
|
|
}
|