|
|
|
@@ -0,0 +1,362 @@
|
|
|
|
|
#nullable enable
|
|
|
|
|
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using ExampleGreedMod;
|
|
|
|
|
using NUnit.Framework;
|
|
|
|
|
using ReplacedPerson.Core;
|
|
|
|
|
|
|
|
|
|
namespace ReplacedPerson.Tests
|
|
|
|
|
{
|
|
|
|
|
public sealed class ReplacedPersonCoreTests
|
|
|
|
|
{
|
|
|
|
|
[Test]
|
|
|
|
|
public void Pcg32_IsDeterministic()
|
|
|
|
|
{
|
|
|
|
|
var a = new Pcg32(12345);
|
|
|
|
|
var b = new Pcg32(12345);
|
|
|
|
|
CollectionAssert.AreEqual(Enumerable.Range(0, 32).Select(_ => a.NextUInt()).ToArray(),
|
|
|
|
|
Enumerable.Range(0, 32).Select(_ => b.NextUInt()).ToArray());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
|
public void BuiltInDeck_IsValid()
|
|
|
|
|
{
|
|
|
|
|
var content = ReplacedBuiltInContent.Create();
|
|
|
|
|
var starter = ReplacedBuiltInContent.CreateStarterDeck();
|
|
|
|
|
Assert.That(ReplacedDeckValidator.Validate(starter, content), Is.Empty);
|
|
|
|
|
Assert.That(starter.NormalCards.Count(id => content.Cards[id].EffectType == ReplacedEffectType.Counter), Is.EqualTo(6));
|
|
|
|
|
Assert.That(starter.NormalCards.Count(id => content.Cards[id].EffectType == ReplacedEffectType.Dodge), Is.EqualTo(2));
|
|
|
|
|
Assert.That(starter.NormalCards.Count(id => content.Cards[id].EffectType == ReplacedEffectType.Attack), Is.EqualTo(4));
|
|
|
|
|
Assert.That(content.Enemies.ContainsKey("greed.tutorial"));
|
|
|
|
|
Assert.That(content.Enemies.ContainsKey("greed.full"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
|
public void Match_StartsWithFourNormalAndOneEnd()
|
|
|
|
|
{
|
|
|
|
|
var engine = BuiltInEngine(1);
|
|
|
|
|
Assert.That(engine.State.Round, Is.EqualTo(1));
|
|
|
|
|
Assert.That(engine.State.Players[0].NormalHand, Has.Count.EqualTo(4));
|
|
|
|
|
Assert.That(engine.State.Players[0].EndHand, Has.Count.EqualTo(1));
|
|
|
|
|
Assert.That(engine.State.Phase, Is.EqualTo(ReplacedMatchPhase.AwaitingLead));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
|
public void ResolvedSlots_RemainVisibleDuringTheNextRound()
|
|
|
|
|
{
|
|
|
|
|
var setup = FixedEngine(ReplacedEffectType.Attack, 4, ReplacedEffectType.Counter, 3, 31);
|
|
|
|
|
ResolveOneCardRound(setup.engine, setup.cardOne, setup.cardTwo);
|
|
|
|
|
Assert.That(setup.engine.State.Round, Is.EqualTo(2));
|
|
|
|
|
Assert.That(setup.engine.State.LastResolution, Has.Count.EqualTo(1));
|
|
|
|
|
Assert.That(setup.engine.State.LastResolution[0].Summary, Is.Not.Empty);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
|
public void Command_RejectsWrongPlayerAndDeduplicatesAcceptedToken()
|
|
|
|
|
{
|
|
|
|
|
var engine = BuiltInEngine(2);
|
|
|
|
|
var wrong = 1 - engine.State.LeadPlayer;
|
|
|
|
|
var rejected = engine.Apply(Command(engine, wrong, Pass(), 1, "wrong"));
|
|
|
|
|
Assert.That(rejected.ErrorCode, Is.EqualTo("turn.not-yours"));
|
|
|
|
|
var lead = engine.State.LeadPlayer;
|
|
|
|
|
var accepted = Command(engine, lead, Pass(), 1, "same");
|
|
|
|
|
Assert.That(engine.Apply(accepted).Accepted, Is.True);
|
|
|
|
|
var duplicate = engine.Apply(accepted);
|
|
|
|
|
Assert.That(duplicate.Accepted, Is.True);
|
|
|
|
|
Assert.That(duplicate.Duplicate, Is.True);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
|
public void AttackAgainstDodge_DealsDifferenceCappedAtEight()
|
|
|
|
|
{
|
|
|
|
|
var setup = FixedEngine(ReplacedEffectType.Attack, 20, ReplacedEffectType.Dodge, 1, 3);
|
|
|
|
|
ResolveOneCardRound(setup.engine, setup.cardOne, setup.cardTwo);
|
|
|
|
|
Assert.That(setup.engine.State.Players[1].Health, Is.EqualTo(22));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
|
public void CounterAgainstAttack_CancelsAndReturnsDamageCappedAtSix()
|
|
|
|
|
{
|
|
|
|
|
var setup = FixedEngine(ReplacedEffectType.Counter, 20, ReplacedEffectType.Attack, 1, 4);
|
|
|
|
|
ResolveOneCardRound(setup.engine, setup.cardOne, setup.cardTwo);
|
|
|
|
|
Assert.That(setup.engine.State.Players[1].Health, Is.EqualTo(24));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
|
public void DodgeAgainstAttack_GrantsExtraDraw()
|
|
|
|
|
{
|
|
|
|
|
var setup = FixedEngine(ReplacedEffectType.Dodge, 20, ReplacedEffectType.Attack, 1, 5);
|
|
|
|
|
ResolveOneCardRound(setup.engine, setup.cardOne, setup.cardTwo);
|
|
|
|
|
Assert.That(setup.engine.State.Players[0].NormalHand.Count, Is.EqualTo(6));
|
|
|
|
|
Assert.That(setup.engine.State.Players[0].Health, Is.EqualTo(30));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
|
public void DoubleAttackFusion_UsesOnePointFiveMultiplier()
|
|
|
|
|
{
|
|
|
|
|
var setup = FixedEngine(ReplacedEffectType.Attack, 2, ReplacedEffectType.Dodge, 1, 6);
|
|
|
|
|
var state = setup.engine.State;
|
|
|
|
|
state.Players[0].NormalHand.Clear();
|
|
|
|
|
state.Players[0].NormalHand.AddRange(new[] { setup.cardOne, setup.cardOne, setup.cardOne, setup.cardOne });
|
|
|
|
|
var first = new ReplacedTurnSubmission
|
|
|
|
|
{
|
|
|
|
|
NormalCardIds = new List<string> { setup.cardOne, setup.cardOne, setup.cardOne, setup.cardOne },
|
|
|
|
|
EndCardIds = new List<string> { state.Players[0].EndHand[0] }
|
|
|
|
|
};
|
|
|
|
|
ResolveRound(setup.engine, first, Pass());
|
|
|
|
|
Assert.That(setup.engine.State.Players[1].Health, Is.EqualTo(20));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
|
public void Pass_SuppressesNextEndDrawAndPaysNoCost()
|
|
|
|
|
{
|
|
|
|
|
var engine = BuiltInEngine(7);
|
|
|
|
|
var endsBefore = engine.State.Players[0].EndHand.Count;
|
|
|
|
|
ResolveRound(engine, Pass(), Pass());
|
|
|
|
|
Assert.That(engine.State.Players[0].Cost, Is.EqualTo(10));
|
|
|
|
|
Assert.That(engine.State.Players[0].EndHand.Count, Is.EqualTo(endsBefore));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
|
public void Cost_IsPaidAtLockAndRecoveredAtRoundEnd()
|
|
|
|
|
{
|
|
|
|
|
var engine = BuiltInEngine(8);
|
|
|
|
|
var firstPlayer = engine.State.LeadPlayer;
|
|
|
|
|
var player = engine.State.Players[firstPlayer];
|
|
|
|
|
var affordable = player.NormalHand.First(id => engine.Content.Cards[id].Cost <= player.Cost);
|
|
|
|
|
var ending = player.EndHand[0];
|
|
|
|
|
var spent = engine.Content.Cards[affordable].Cost + engine.Content.EndCards[ending].Cost;
|
|
|
|
|
var result = engine.Apply(Command(engine, firstPlayer, Submit(affordable, ending), 1, "lock"));
|
|
|
|
|
Assert.That(result.Accepted);
|
|
|
|
|
Assert.That(player.Cost, Is.EqualTo(10 - spent));
|
|
|
|
|
var other = 1 - firstPlayer;
|
|
|
|
|
engine.Apply(Command(engine, other, Pass(), 1, "other"));
|
|
|
|
|
Assert.That(player.Cost, Is.EqualTo(Math.Min(10, 10 - spent + 1 + spent / 2)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
|
public void SimultaneousLethal_EntersLastStand()
|
|
|
|
|
{
|
|
|
|
|
var setup = FixedEngine(ReplacedEffectType.Attack, 3, ReplacedEffectType.Attack, 1, 9);
|
|
|
|
|
var engine = setup.engine;
|
|
|
|
|
var strong = setup.cardOne;
|
|
|
|
|
var weak = setup.cardTwo;
|
|
|
|
|
engine.State.Players[0].Health = 1;
|
|
|
|
|
engine.State.Players[1].Health = 1;
|
|
|
|
|
engine.State.Players[0].NormalHand.Clear();
|
|
|
|
|
engine.State.Players[1].NormalHand.Clear();
|
|
|
|
|
engine.State.Players[0].NormalHand.AddRange(new[] { strong, weak });
|
|
|
|
|
engine.State.Players[1].NormalHand.AddRange(new[] { weak, strong });
|
|
|
|
|
var end0 = engine.State.Players[0].EndHand[0];
|
|
|
|
|
var end1 = engine.State.Players[1].EndHand[0];
|
|
|
|
|
ResolveRound(engine,
|
|
|
|
|
new ReplacedTurnSubmission { NormalCardIds = new List<string> { strong, weak }, EndCardIds = new List<string> { end0 } },
|
|
|
|
|
new ReplacedTurnSubmission { NormalCardIds = new List<string> { weak, strong }, EndCardIds = new List<string> { end1 } });
|
|
|
|
|
Assert.That(engine.State.IsLastStand, Is.True);
|
|
|
|
|
Assert.That(engine.State.LastStandUsed, Is.True);
|
|
|
|
|
Assert.That(engine.State.Players[0].NormalHand, Has.Count.EqualTo(4));
|
|
|
|
|
Assert.That(engine.State.Players[0].EndHand, Has.Count.EqualTo(2));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
|
public void Replay_ProducesIdenticalStateHash()
|
|
|
|
|
{
|
|
|
|
|
var content = ReplacedBuiltInContent.Create();
|
|
|
|
|
var deck = ReplacedBuiltInContent.CreateStarterDeck();
|
|
|
|
|
var original = new ReplacedMatchEngine(content, deck, deck, 77);
|
|
|
|
|
ResolveRound(original, Pass(), Pass());
|
|
|
|
|
ResolveRound(original, Pass(), Pass());
|
|
|
|
|
var replay = ReplacedReplayRunner.Replay(content, deck, deck, 77, original.Replay);
|
|
|
|
|
Assert.That(replay.ComputeStateHash(), Is.EqualTo(original.ComputeStateHash()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
|
public void StateHash_IncludesVisibleSlotResolution()
|
|
|
|
|
{
|
|
|
|
|
var engine = BuiltInEngine(91);
|
|
|
|
|
var before = engine.ComputeStateHash();
|
|
|
|
|
engine.State.LastResolution.Add(new ReplacedSlotResolution
|
|
|
|
|
{
|
|
|
|
|
SlotIndex = 0,
|
|
|
|
|
PlayerOneType = ReplacedEffectType.Counter,
|
|
|
|
|
PlayerTwoType = ReplacedEffectType.Attack,
|
|
|
|
|
PlayerOneRoll = 5,
|
|
|
|
|
PlayerTwoRoll = 2,
|
|
|
|
|
PlayerOneValue = 17,
|
|
|
|
|
PlayerTwoValue = 8,
|
|
|
|
|
DamageToPlayerTwo = 6,
|
|
|
|
|
Summary = "hash-visible-resolution"
|
|
|
|
|
});
|
|
|
|
|
Assert.That(engine.ComputeStateHash(), Is.Not.EqualTo(before));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
|
public void Ai_ProducesLegalSubmissionWithoutMutatingVisibleState()
|
|
|
|
|
{
|
|
|
|
|
var engine = BuiltInEngine(12);
|
|
|
|
|
var aiPlayer = engine.State.LeadPlayer;
|
|
|
|
|
var before = engine.ComputeStateHash();
|
|
|
|
|
var submission = new ReplacedAi(engine.Content).Choose(engine.State.Clone(), aiPlayer);
|
|
|
|
|
Assert.That(engine.ComputeStateHash(), Is.EqualTo(before));
|
|
|
|
|
Assert.That(engine.Apply(Command(engine, aiPlayer, submission, 1, "ai")).Accepted, Is.True);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
|
public void SaveMigration_AddsFirstOrnamentForLegacyProgress()
|
|
|
|
|
{
|
|
|
|
|
var save = new ReplacedPersonSaveData { Version = 1, ChapterProgress = 1, Ornaments = new List<string>() };
|
|
|
|
|
ReplacedPersonSaveMigration.Migrate(save);
|
|
|
|
|
Assert.That(save.Version, Is.EqualTo(3));
|
|
|
|
|
Assert.That(save.Ornaments, Contains.Item("ornament.gold-eye"));
|
|
|
|
|
Assert.That(save.ActiveDeck.NormalCards.Count(id => id == "counter.stance"), Is.EqualTo(2));
|
|
|
|
|
Assert.That(save.CollectedCards.Count(id => id == "counter.stance"), Is.EqualTo(2));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
|
public void ModHandshake_ReportsVersionAndFileHashDifferences()
|
|
|
|
|
{
|
|
|
|
|
var expected = Manifest("a", "1", "x.dll", "aa");
|
|
|
|
|
var actual = Manifest("a", "2", "x.dll", "bb");
|
|
|
|
|
var result = ReplacedModCompatibility.Compare(new[] { expected }, new[] { actual });
|
|
|
|
|
Assert.That(result.Compatible, Is.False);
|
|
|
|
|
Assert.That(result.Differences.Any(value => value.StartsWith("version:", StringComparison.Ordinal)));
|
|
|
|
|
Assert.That(result.Differences.Any(value => value.StartsWith("hash:", StringComparison.Ordinal)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
|
public void HotReload_DoesNotChangeCurrentMatchSnapshot()
|
|
|
|
|
{
|
|
|
|
|
var catalog = ReplacedBuiltInContent.Create();
|
|
|
|
|
var hotReload = new ReplacedHotReloadCatalog(catalog);
|
|
|
|
|
var registry = new ReplacedPersonRegistry(catalog);
|
|
|
|
|
new ExampleGreedRules().Register(registry);
|
|
|
|
|
var next = registry.CreateSnapshot();
|
|
|
|
|
Assert.That(next.ComputeHash(), Is.Not.EqualTo(catalog.ComputeHash()));
|
|
|
|
|
hotReload.Stage(next);
|
|
|
|
|
Assert.That(hotReload.CurrentHash, Is.EqualTo(catalog.ComputeHash()));
|
|
|
|
|
var promoted = hotReload.SnapshotForNewMatch();
|
|
|
|
|
Assert.That(hotReload.Pending, Is.Null);
|
|
|
|
|
Assert.That(promoted.Cards, Contains.Key("example.audit"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
|
public void PlayerSnapshot_HidesOpponentCardsButPreservesCounts()
|
|
|
|
|
{
|
|
|
|
|
var room = StartedRoom();
|
|
|
|
|
var expectedNormalCount = room.Match!.State.Players[1].NormalHand.Count;
|
|
|
|
|
var snapshot = room.PlayerMatchSnapshot("a")!;
|
|
|
|
|
Assert.That(snapshot.Players[1].NormalHand, Has.Count.EqualTo(expectedNormalCount));
|
|
|
|
|
Assert.That(snapshot.Players[1].NormalHand.All(string.IsNullOrEmpty));
|
|
|
|
|
Assert.That(snapshot.Players[0].NormalHand.All(value => !string.IsNullOrEmpty(value)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
|
public void RoomAuthority_StartsOnlyAfterTwoValidReadyDecks()
|
|
|
|
|
{
|
|
|
|
|
var content = ReplacedBuiltInContent.Create();
|
|
|
|
|
var room = new ReplacedRoomAuthority("test", content, Array.Empty<ReplacedPersonModManifest>(), 20);
|
|
|
|
|
Assert.That(room.TryJoin(Join("a"), DateTime.UtcNow).Compatible);
|
|
|
|
|
Assert.That(room.TryJoin(Join("b"), DateTime.UtcNow).Compatible);
|
|
|
|
|
Assert.That(room.SetDeckAndReady("a", ReplacedBuiltInContent.CreateStarterDeck()), Is.Empty);
|
|
|
|
|
Assert.That(room.Match, Is.Null);
|
|
|
|
|
Assert.That(room.SetDeckAndReady("b", ReplacedBuiltInContent.CreateStarterDeck()), Is.Empty);
|
|
|
|
|
Assert.That(room.Match, Is.Not.Null);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Test]
|
|
|
|
|
public void TwoConsecutiveTimeouts_ForfeitMatch()
|
|
|
|
|
{
|
|
|
|
|
var content = ReplacedBuiltInContent.Create();
|
|
|
|
|
var room = new ReplacedRoomAuthority("test", content, Array.Empty<ReplacedPersonModManifest>(), 21);
|
|
|
|
|
room.TryJoin(Join("a"), DateTime.UtcNow); room.TryJoin(Join("b"), DateTime.UtcNow);
|
|
|
|
|
room.SetDeckAndReady("a", ReplacedBuiltInContent.CreateStarterDeck()); room.SetDeckAndReady("b", ReplacedBuiltInContent.CreateStarterDeck());
|
|
|
|
|
room.RecordTurnTimeout("a"); room.RecordTurnTimeout("a");
|
|
|
|
|
Assert.That(room.Match!.State.Winner, Is.EqualTo(ReplacedWinner.PlayerTwo));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static ReplacedMatchEngine BuiltInEngine(ulong seed)
|
|
|
|
|
{
|
|
|
|
|
var deck = ReplacedBuiltInContent.CreateStarterDeck();
|
|
|
|
|
return new ReplacedMatchEngine(ReplacedBuiltInContent.Create(), deck, deck, seed);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static ReplacedRoomAuthority StartedRoom()
|
|
|
|
|
{
|
|
|
|
|
var room = new ReplacedRoomAuthority("test", ReplacedBuiltInContent.Create(),
|
|
|
|
|
Array.Empty<ReplacedPersonModManifest>(), 22);
|
|
|
|
|
room.TryJoin(Join("a"), DateTime.UtcNow); room.TryJoin(Join("b"), DateTime.UtcNow);
|
|
|
|
|
room.SetDeckAndReady("a", ReplacedBuiltInContent.CreateStarterDeck());
|
|
|
|
|
room.SetDeckAndReady("b", ReplacedBuiltInContent.CreateStarterDeck());
|
|
|
|
|
return room;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static (ReplacedMatchEngine engine, string cardOne, string cardTwo) FixedEngine(
|
|
|
|
|
ReplacedEffectType firstType, int firstValue, ReplacedEffectType secondType, int secondValue, ulong seed)
|
|
|
|
|
{
|
|
|
|
|
var content = new ReplacedContentCatalog();
|
|
|
|
|
var firstIds = AddSixCards(content, "one", firstType, firstValue);
|
|
|
|
|
var secondIds = AddSixCards(content, "two", secondType, secondValue);
|
|
|
|
|
var ends = new List<string>();
|
|
|
|
|
for (var i = 0; i < 4; i++)
|
|
|
|
|
{
|
|
|
|
|
var id = "end." + i;
|
|
|
|
|
content.EndCards.Add(id, new ReplacedEndCardDefinition { Id = id, Name = id, SlotBase = new int[3], SlotFactor = new int[3] });
|
|
|
|
|
ends.Add(id);
|
|
|
|
|
}
|
|
|
|
|
var firstDeck = new ReplacedDeck { NormalCards = firstIds.SelectMany(id => new[] { id, id }).ToList(), EndCards = new List<string>(ends) };
|
|
|
|
|
var secondDeck = new ReplacedDeck { NormalCards = secondIds.SelectMany(id => new[] { id, id }).ToList(), EndCards = new List<string>(ends) };
|
|
|
|
|
var engine = new ReplacedMatchEngine(content, firstDeck, secondDeck, seed);
|
|
|
|
|
return (engine, engine.State.Players[0].NormalHand[0], engine.State.Players[1].NormalHand[0]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static List<string> AddSixCards(ReplacedContentCatalog content, string prefix, ReplacedEffectType type, int value)
|
|
|
|
|
{
|
|
|
|
|
var result = new List<string>();
|
|
|
|
|
for (var i = 0; i < 6; i++)
|
|
|
|
|
{
|
|
|
|
|
var id = prefix + "." + i;
|
|
|
|
|
content.Cards.Add(id, new ReplacedCardDefinition { Id = id, Name = id, EffectType = type, BaseValue = value, Factor = 0, Cost = 0 });
|
|
|
|
|
result.Add(id);
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void ResolveOneCardRound(ReplacedMatchEngine engine, string first, string second)
|
|
|
|
|
{
|
|
|
|
|
ResolveRound(engine, Submit(first, engine.State.Players[0].EndHand[0]), Submit(second, engine.State.Players[1].EndHand[0]));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void ResolveRound(ReplacedMatchEngine engine, ReplacedTurnSubmission first, ReplacedTurnSubmission second)
|
|
|
|
|
{
|
|
|
|
|
var byPlayer = new[] { first, second };
|
|
|
|
|
var lead = engine.State.LeadPlayer;
|
|
|
|
|
Assert.That(engine.Apply(Command(engine, lead, byPlayer[lead], engine.State.LastAcceptedSequence[lead] + 1, Guid.NewGuid().ToString("N"))).Accepted);
|
|
|
|
|
var response = 1 - lead;
|
|
|
|
|
Assert.That(engine.Apply(Command(engine, response, byPlayer[response], engine.State.LastAcceptedSequence[response] + 1, Guid.NewGuid().ToString("N"))).Accepted);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static ReplacedMatchCommand Command(ReplacedMatchEngine engine, int player, ReplacedTurnSubmission submission, long sequence, string token) => new()
|
|
|
|
|
{
|
|
|
|
|
PlayerIndex = player, Submission = submission, Sequence = sequence, IdempotencyToken = token
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
private static ReplacedTurnSubmission Submit(string card, string ending) => new()
|
|
|
|
|
{
|
|
|
|
|
NormalCardIds = new List<string> { card }, EndCardIds = new List<string> { ending }
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
private static ReplacedTurnSubmission Pass() => new() { Pass = true };
|
|
|
|
|
|
|
|
|
|
private static ReplacedPersonModManifest Manifest(string id, string version, string path, string hash) => new()
|
|
|
|
|
{
|
|
|
|
|
Id = id, Version = version, Files = new List<ReplacedPersonModFile> { new() { Path = path, Sha256 = hash } }
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
private static ReplacedJoinRequest Join(string id) => new()
|
|
|
|
|
{
|
|
|
|
|
RoomId = "test", PlayerId = id, DisplayName = id, Mods = new List<ReplacedPersonModManifest>()
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|