186 lines
5.5 KiB
C#
186 lines
5.5 KiB
C#
using Vanguard.Content;
|
|
using Vanguard.Rules.Cards;
|
|
using Vanguard.Rules.Decks;
|
|
using Vanguard.Server.Configuration;
|
|
|
|
namespace Vanguard.Server.Tests;
|
|
|
|
internal static class ServerTestData
|
|
{
|
|
private static readonly Lazy<CardCatalog> LazyCatalog = new(
|
|
() => CatalogLoader.LoadDirectory(ContentPaths.Resolve(null)),
|
|
isThreadSafe: true);
|
|
|
|
private static readonly Lazy<TrialDeckCatalog> LazyTrialDecks = new(
|
|
() => TrialDeckCatalog.Load(
|
|
Path.Combine(Path.GetDirectoryName(ContentPaths.Resolve(null))!, "trial-decks.yaml")),
|
|
isThreadSafe: true);
|
|
|
|
public static CardCatalog Catalog => LazyCatalog.Value;
|
|
|
|
public static TrialDeckCatalog TrialDecks => LazyTrialDecks.Value;
|
|
|
|
/// <summary>Колода без Sentinel и Heal, ровно 50 карт, до 4 копий на nameId.</summary>
|
|
public static List<string> BuildDeck()
|
|
{
|
|
var deck = new List<string>();
|
|
var counts = new Dictionary<string, int>(StringComparer.Ordinal);
|
|
|
|
void Add(string printingId, int limit)
|
|
{
|
|
var card = Catalog.CardOf(printingId);
|
|
counts.TryGetValue(card.NameId, out var used);
|
|
var room = Math.Min(Math.Min(4 - used, limit), 50 - deck.Count);
|
|
for (var i = 0; i < room; i++)
|
|
deck.Add(printingId);
|
|
counts[card.NameId] = used + room;
|
|
}
|
|
|
|
// Сначала ровно 16 триггеров, Heal — не больше четырёх.
|
|
var triggers = 0;
|
|
var heals = 0;
|
|
foreach (var group in Catalog.Printings
|
|
.Where(p => !p.IsParallel)
|
|
.GroupBy(p => p.NameId)
|
|
.OrderBy(g => g.Key, StringComparer.Ordinal))
|
|
{
|
|
if (triggers >= DeckValidator.RequiredTriggers)
|
|
break;
|
|
|
|
var card = Catalog.CardOf(group.First().PrintingId);
|
|
if (card.IsSentinel || card.Trigger == TriggerType.None)
|
|
continue;
|
|
if (card.Trigger == TriggerType.Heal && heals >= DeckValidator.MaxHeal)
|
|
continue;
|
|
|
|
var before = deck.Count;
|
|
Add(group.First().PrintingId, Math.Min(
|
|
DeckValidator.RequiredTriggers - triggers,
|
|
card.Trigger == TriggerType.Heal
|
|
? DeckValidator.MaxHeal - heals
|
|
: DeckValidator.RequiredTriggers));
|
|
var added = deck.Count - before;
|
|
triggers += added;
|
|
if (card.Trigger == TriggerType.Heal)
|
|
heals += added;
|
|
}
|
|
|
|
// Остальное — обычные воины.
|
|
foreach (var group in Catalog.Printings
|
|
.Where(p => !p.IsParallel)
|
|
.GroupBy(p => p.NameId)
|
|
.OrderBy(g => g.Key, StringComparer.Ordinal))
|
|
{
|
|
if (deck.Count >= 50)
|
|
break;
|
|
|
|
var card = Catalog.CardOf(group.First().PrintingId);
|
|
if (card.IsSentinel || card.Trigger != TriggerType.None)
|
|
continue;
|
|
|
|
Add(group.First().PrintingId, 4);
|
|
}
|
|
|
|
Assert.Equal(50, deck.Count);
|
|
return deck;
|
|
}
|
|
|
|
public static GameOptions SmallOptions() => new()
|
|
{
|
|
TurnSeconds = 90,
|
|
BankSeconds = 300,
|
|
AbsenceSeconds = 300,
|
|
ClockSyncSeconds = 5,
|
|
TicketTtlSeconds = 60,
|
|
ConnectTimeoutSeconds = 5,
|
|
CompletedMatchTtlSeconds = 600,
|
|
QueueExpirySeconds = 600,
|
|
MmrInitialWindow = 100,
|
|
MmrWindowStep = 50,
|
|
MmrWindowIntervalSeconds = 5,
|
|
MmrMaxWindow = 2000,
|
|
};
|
|
}
|
|
|
|
internal sealed class FakeTimeProvider : TimeProvider
|
|
{
|
|
private DateTimeOffset _now;
|
|
|
|
public FakeTimeProvider(DateTimeOffset now) => _now = now;
|
|
|
|
public override DateTimeOffset GetUtcNow() => _now;
|
|
|
|
public void Advance(TimeSpan delta) => _now += delta;
|
|
}
|
|
|
|
internal sealed class FakeMatchConnection : Matches.IMatchConnection
|
|
{
|
|
private readonly object _gate = new();
|
|
private readonly List<Vanguard.Shared.Protocol.ServerFrame> _frames = new();
|
|
|
|
public string Id { get; } = Guid.NewGuid().ToString("N");
|
|
public bool IsClosed { get; private set; }
|
|
public int CloseCode { get; private set; }
|
|
|
|
public IReadOnlyList<Vanguard.Shared.Protocol.ServerFrame> Frames
|
|
{
|
|
get
|
|
{
|
|
lock (_gate)
|
|
return _frames.ToList();
|
|
}
|
|
}
|
|
|
|
public Task SendAsync(
|
|
Vanguard.Shared.Protocol.ServerFrame frame,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
lock (_gate)
|
|
_frames.Add(frame);
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
public Task CloseAsync(int code, string reason)
|
|
{
|
|
IsClosed = true;
|
|
CloseCode = code;
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
public T Last<T>() where T : Vanguard.Shared.Protocol.ServerFrame
|
|
{
|
|
lock (_gate)
|
|
return _frames.OfType<T>().Last();
|
|
|
|
}
|
|
|
|
public IReadOnlyList<T> All<T>() where T : Vanguard.Shared.Protocol.ServerFrame
|
|
{
|
|
lock (_gate)
|
|
return _frames.OfType<T>().ToList();
|
|
}
|
|
}
|
|
|
|
internal sealed class RecordingResultSink : Matches.IMatchResultSink
|
|
{
|
|
private readonly List<Matches.MatchResult> _results = new();
|
|
|
|
public IReadOnlyList<Matches.MatchResult> Results
|
|
{
|
|
get
|
|
{
|
|
lock (_results)
|
|
return _results.ToList();
|
|
}
|
|
}
|
|
|
|
public Task RecordAsync(Matches.MatchResult result)
|
|
{
|
|
lock (_results)
|
|
_results.Add(result);
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
}
|