174 lines
5.4 KiB
C#
174 lines
5.4 KiB
C#
using Vanguard.Rules.Cards;
|
|
using Vanguard.Rules.Decks;
|
|
using Xunit;
|
|
|
|
namespace Vanguard.Rules.Tests;
|
|
|
|
public class DeckValidatorTests
|
|
{
|
|
private readonly CardCatalog _catalog = TestCatalog.Instance;
|
|
|
|
[Fact]
|
|
public void Legal_Deck_Has_No_Violations()
|
|
{
|
|
var deck = BuildLegalDeck();
|
|
|
|
Assert.Empty(DeckValidator.Validate(_catalog, deck));
|
|
}
|
|
|
|
[Fact]
|
|
public void Deck_Must_Have_Fifty_Cards()
|
|
{
|
|
var deck = BuildLegalDeck();
|
|
deck.RemoveAt(0);
|
|
|
|
var violations = DeckValidator.Validate(_catalog, deck);
|
|
|
|
Assert.Contains(violations, v => v.Code == "deck.size");
|
|
}
|
|
|
|
[Fact]
|
|
public void Copy_Limit_Counts_By_Name_Across_Printings()
|
|
{
|
|
var deck = DeckWith(("BT01/002EN", 4), ("TD01/005EN", 4));
|
|
|
|
var violations = DeckValidator.Validate(_catalog, deck);
|
|
|
|
var copies = Assert.Single(violations, v => v.Code == "deck.copies");
|
|
Assert.Contains("Blaster Blade", copies.Message);
|
|
}
|
|
|
|
[Fact]
|
|
public void Copy_Limit_Counts_Parallel_Art_As_Same_Card()
|
|
{
|
|
var deck = DeckWith(("BT01/002EN", 4), ("BT01/S02EN", 4));
|
|
|
|
var violations = DeckValidator.Validate(_catalog, deck);
|
|
|
|
Assert.Contains(violations, v => v.Code == "deck.copies");
|
|
}
|
|
|
|
[Fact]
|
|
public void Sentinel_Limit_Is_Four()
|
|
{
|
|
var deck = DeckWith(("BT01/011EN", 4), ("BT01/015EN", 1));
|
|
|
|
var violations = DeckValidator.Validate(_catalog, deck);
|
|
|
|
Assert.Contains(violations, v => v.Code == "deck.sentinel");
|
|
}
|
|
|
|
[Fact]
|
|
public void Heal_Limit_Is_Four()
|
|
{
|
|
var deck = DeckWith(("BT01/047EN", 4), ("BT01/053EN", 1));
|
|
|
|
var violations = DeckValidator.Validate(_catalog, deck);
|
|
|
|
Assert.Contains(violations, v => v.Code == "deck.heal");
|
|
}
|
|
|
|
[Fact]
|
|
public void Exactly_Sixteen_Triggers_Are_Required()
|
|
{
|
|
var deck = BuildLegalDeck();
|
|
var triggerIndex = deck.FindIndex(id =>
|
|
_catalog.CardOf(id).Trigger != TriggerType.None);
|
|
Assert.True(triggerIndex >= 0);
|
|
|
|
deck[triggerIndex] = "BT01/042EN"; // Marron, no trigger
|
|
|
|
var violations = DeckValidator.Validate(_catalog, deck);
|
|
Assert.Contains(violations, v => v.Code == "deck.triggers");
|
|
}
|
|
|
|
[Fact]
|
|
public void Unknown_Printing_Is_Reported()
|
|
{
|
|
var deck = BuildLegalDeck();
|
|
deck[0] = "BT99/999EN";
|
|
|
|
var violations = DeckValidator.Validate(_catalog, deck);
|
|
|
|
Assert.Contains(violations, v => v.Code == "deck.unknown-printing");
|
|
}
|
|
|
|
private List<string> BuildLegalDeck() => DeckWith();
|
|
|
|
/// <summary>
|
|
/// Собирает колоду на 50 карт: сначала заданные принты, затем по 4 копии
|
|
/// каждого имени, кроме Sentinel и Heal (чтобы не упираться в их лимиты).
|
|
/// </summary>
|
|
private List<string> DeckWith(params (string PrintingId, int Count)[] seed)
|
|
{
|
|
var deck = new List<string>();
|
|
var usedNames = new HashSet<string>(StringComparer.Ordinal);
|
|
|
|
foreach (var (printingId, count) in seed)
|
|
{
|
|
deck.AddRange(Enumerable.Repeat(printingId, count));
|
|
usedNames.Add(_catalog.Printing(printingId).NameId);
|
|
}
|
|
|
|
var triggers = deck.Count(id => _catalog.CardOf(id).Trigger != TriggerType.None);
|
|
var heals = deck.Count(id => _catalog.CardOf(id).Trigger == TriggerType.Heal);
|
|
|
|
foreach (var group in _catalog.Printings
|
|
.Where(p => !p.IsParallel)
|
|
.GroupBy(p => p.NameId)
|
|
.OrderBy(g => g.Key, StringComparer.Ordinal))
|
|
{
|
|
if (triggers >= DeckValidator.RequiredTriggers || deck.Count >= DeckValidator.RequiredSize)
|
|
break;
|
|
|
|
if (usedNames.Contains(group.Key))
|
|
continue;
|
|
|
|
var card = _catalog.Card(group.Key);
|
|
if (card.IsSentinel || card.Trigger == TriggerType.None)
|
|
continue;
|
|
if (card.Trigger == TriggerType.Heal && heals >= DeckValidator.MaxHeal)
|
|
continue;
|
|
|
|
var printingId = group.First().PrintingId;
|
|
usedNames.Add(group.Key);
|
|
|
|
var copies = Math.Min(
|
|
DeckValidator.MaxCopiesPerName,
|
|
Math.Min(DeckValidator.RequiredTriggers - triggers,
|
|
DeckValidator.RequiredSize - deck.Count));
|
|
for (var i = 0; i < copies; i++)
|
|
{
|
|
deck.Add(printingId);
|
|
triggers++;
|
|
if (card.Trigger == TriggerType.Heal)
|
|
heals++;
|
|
}
|
|
}
|
|
|
|
foreach (var group in _catalog.Printings
|
|
.Where(p => !p.IsParallel)
|
|
.GroupBy(p => p.NameId)
|
|
.OrderBy(g => g.Key, StringComparer.Ordinal))
|
|
{
|
|
if (deck.Count >= DeckValidator.RequiredSize)
|
|
break;
|
|
|
|
if (usedNames.Contains(group.Key))
|
|
continue;
|
|
|
|
var card = _catalog.Card(group.Key);
|
|
if (card.IsSentinel || card.Trigger != TriggerType.None)
|
|
continue;
|
|
|
|
var printingId = group.First().PrintingId;
|
|
usedNames.Add(group.Key);
|
|
|
|
for (var i = 0; i < DeckValidator.MaxCopiesPerName && deck.Count < DeckValidator.RequiredSize; i++)
|
|
deck.Add(printingId);
|
|
}
|
|
|
|
return deck;
|
|
}
|
|
}
|