Add bounds hazards and triggers
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
using System.Collections.Immutable;
|
||||
using SideScrollerGame.Sim.Definitions;
|
||||
using SideScrollerGame.Sim.Input;
|
||||
|
||||
namespace SideScrollerGame.Sim.Tests;
|
||||
@@ -7,7 +9,7 @@ public sealed class SimulationSerializationTests
|
||||
[Fact]
|
||||
public void SaveStateLoadState_PreservesStateAndNextStepHash()
|
||||
{
|
||||
var definition = SimulationTestFactory.CreateGameDefinition();
|
||||
var definition = SimulationTestFactory.CreateGameDefinition(triggers: ImmutableArray.Create(new TriggerDefinition("checkpoint_a", new(new(11, 21), new(12, 22)), "TriggerActivated")));
|
||||
var config = SimulationTestFactory.CreateConfig();
|
||||
|
||||
Simulation original = new(definition, config, 17);
|
||||
@@ -18,6 +20,8 @@ public sealed class SimulationSerializationTests
|
||||
|
||||
Assert.Equal(original.CurrentTick, loaded.CurrentTick);
|
||||
Assert.Equal(original.CurrentSnapshot.StateHash, loaded.CurrentSnapshot.StateHash);
|
||||
Assert.Equal(original.CurrentState.GetRequiredPlayer(new(1)).Health, loaded.CurrentState.GetRequiredPlayer(new(1)).Health);
|
||||
Assert.Equal(original.CurrentState.ActivatedTriggerIds, loaded.CurrentState.ActivatedTriggerIds);
|
||||
|
||||
var nextBatch = SimulationTestFactory.CreateTick(2, new ButtonChanged(new(1), InputButton.FirePrimary, true));
|
||||
var originalHash = original.Step(nextBatch).StateHash;
|
||||
|
||||
@@ -9,38 +9,61 @@ public sealed class SimulationStateTests
|
||||
[Fact]
|
||||
public void PlayerState_CloneAndButtonReleasePreserveIndependentState()
|
||||
{
|
||||
PlayerState player = new(new(1), new(3, 4), 1, 2, 5, 6, 7, 0);
|
||||
PlayerState player = new(new(1), new(3, 4), 1, 2, 5, 6, 7, 0, 9);
|
||||
player.SetButton(InputButton.Dash, true);
|
||||
player.ApplyDamage(4);
|
||||
|
||||
var clone = player.Clone();
|
||||
player.SetButton(InputButton.Dash, false);
|
||||
player.SetPosition(new(8, 9));
|
||||
|
||||
Assert.NotEqual(player.ButtonMask, clone.ButtonMask);
|
||||
Assert.Equal(7, clone.SelectedWeaponSlot);
|
||||
Assert.Equal(3, clone.Position.m_X.ToIntRound());
|
||||
Assert.Equal(4, clone.Position.m_Y.ToIntRound());
|
||||
Assert.Equal(5, clone.Health);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SimulationState_CloneCreatesDeepCopy()
|
||||
{
|
||||
SimulationState original = new(4, 9, 123UL, 456UL, ImmutableArray.Create(new PlayerState(new(1), new(1, 2), 3, 4, 5, 6, 7, 8)));
|
||||
SimulationState original = new(4, 9, 123UL, 456UL, ImmutableArray.Create(new PlayerState(new(1), new(1, 2), 3, 4, 5, 6, 7, 8, 9)), ImmutableHashSet<string>.Empty.Add("checkpoint_a"));
|
||||
var clone = original.Clone();
|
||||
|
||||
original.GetRequiredPlayer(new(1)).SetMoveAxis(9, 9);
|
||||
original.ActivateTrigger("checkpoint_b");
|
||||
|
||||
Assert.Equal(4, clone.Tick);
|
||||
Assert.Equal(9, clone.Seed);
|
||||
Assert.Equal((ulong)123, clone.RandomState);
|
||||
Assert.Equal((ulong)456, clone.LastRandomValue);
|
||||
Assert.Equal(3, clone.GetRequiredPlayer(new(1)).MoveAxisX);
|
||||
Assert.DoesNotContain("checkpoint_b", clone.ActivatedTriggerIds);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SimulationState_AcceptsDefaultPlayerArray()
|
||||
{
|
||||
SimulationState state = new(0, 1, 1UL, 0UL, default);
|
||||
SimulationState state = new(0, 1, 1UL, 0UL, default, ImmutableHashSet<string>.Empty);
|
||||
|
||||
Assert.Empty(state.Players);
|
||||
Assert.Empty(state.ActivatedTriggerIds);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SimulationState_AcceptsDefaultTriggerSet()
|
||||
{
|
||||
SimulationState state = new(0, 1, 1UL, 0UL, ImmutableArray<PlayerState>.Empty, default!);
|
||||
|
||||
Assert.Empty(state.ActivatedTriggerIds);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ActivateTrigger_ReturnsFalseWhenRepeated()
|
||||
{
|
||||
SimulationState state = new(0, 1, 1UL, 0UL, ImmutableArray<PlayerState>.Empty, ImmutableHashSet<string>.Empty);
|
||||
|
||||
Assert.True(state.ActivateTrigger("checkpoint_a"));
|
||||
Assert.False(state.ActivateTrigger("checkpoint_a"));
|
||||
}
|
||||
}
|
||||
@@ -27,11 +27,71 @@ public sealed class SimulationStepTests
|
||||
Assert.Equal(30, player.AimAxisX);
|
||||
Assert.Equal(40, player.AimAxisY);
|
||||
Assert.Equal(3, player.SelectedWeaponSlot);
|
||||
Assert.Equal(10, player.Health);
|
||||
Assert.NotEqual(0, player.ButtonMask);
|
||||
Assert.Equal(result.StateHash, simulation.CurrentSnapshot.StateHash);
|
||||
Assert.NotEqual(0UL, simulation.CurrentSnapshot.LastRandomValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Step_ClampsPlayerToWorldBounds()
|
||||
{
|
||||
Simulation simulation = new(SimulationTestFactory.CreateGameDefinition(new(new(0, 0), new(11, 20))), SimulationTestFactory.CreateConfig(), 7);
|
||||
|
||||
var result = simulation.Step(SimulationTestFactory.CreateTick(1, new MoveAxisChanged(new(1), 5, 0)));
|
||||
|
||||
var player = simulation.CurrentState.GetRequiredPlayer(new(1));
|
||||
Assert.Equal(11, player.Position.m_X.ToIntRound());
|
||||
Assert.Contains(result.Events, static e => e.Kind == "PlayerClamped");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Step_AppliesHazardDamage()
|
||||
{
|
||||
Simulation simulation = new(SimulationTestFactory.CreateGameDefinition(hazards: ImmutableArray.Create(new HazardDefinition("lava", new(new(11, 20), new(12, 21)), 3))), SimulationTestFactory.CreateConfig(), 7);
|
||||
|
||||
var result = simulation.Step(SimulationTestFactory.CreateTick(1, new MoveAxisChanged(new(1), 1, 0)));
|
||||
|
||||
var player = simulation.CurrentState.GetRequiredPlayer(new(1));
|
||||
Assert.Equal(7, player.Health);
|
||||
Assert.Contains(result.Events, static e => e.Kind == "PlayerDamaged");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Step_IgnoresHazardsWhenPlayerIsOutside()
|
||||
{
|
||||
Simulation simulation = new(SimulationTestFactory.CreateGameDefinition(hazards: ImmutableArray.Create(new HazardDefinition("lava", new(new(50, 50), new(60, 60)), 3))), SimulationTestFactory.CreateConfig(), 7);
|
||||
|
||||
var result = simulation.Step(SimulationTestFactory.CreateTick(1, new MoveAxisChanged(new(1), 1, 0)));
|
||||
|
||||
Assert.Equal(10, simulation.CurrentState.GetRequiredPlayer(new(1)).Health);
|
||||
Assert.DoesNotContain(result.Events, static e => e.Kind == "PlayerDamaged");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Step_ActivatesTriggerOnlyOnce()
|
||||
{
|
||||
Simulation simulation = new(SimulationTestFactory.CreateGameDefinition(triggers: ImmutableArray.Create(new TriggerDefinition("checkpoint_a", new(new(11, 20), new(12, 21)), "TriggerActivated"))), SimulationTestFactory.CreateConfig(), 7);
|
||||
|
||||
var first = simulation.Step(SimulationTestFactory.CreateTick(1, new MoveAxisChanged(new(1), 1, 0)));
|
||||
var second = simulation.Step(SimulationTestFactory.CreateTick(2, new MoveAxisChanged(new(1), 0, 0)));
|
||||
|
||||
Assert.Contains(first.Events, static e => e.Kind == "TriggerActivated");
|
||||
Assert.DoesNotContain(second.Events, static e => e.Kind == "TriggerActivated");
|
||||
Assert.Contains("checkpoint_a", simulation.CurrentState.ActivatedTriggerIds);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Step_IgnoresTriggersWhenPlayerIsOutside()
|
||||
{
|
||||
Simulation simulation = new(SimulationTestFactory.CreateGameDefinition(triggers: ImmutableArray.Create(new TriggerDefinition("checkpoint_a", new(new(50, 50), new(60, 60)), "TriggerActivated"))), SimulationTestFactory.CreateConfig(), 7);
|
||||
|
||||
var result = simulation.Step(SimulationTestFactory.CreateTick(1, new MoveAxisChanged(new(1), 1, 0)));
|
||||
|
||||
Assert.DoesNotContain(result.Events, static e => e.Kind == "TriggerActivated");
|
||||
Assert.Empty(simulation.CurrentState.ActivatedTriggerIds);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Step_RejectsUnexpectedTickNumbers()
|
||||
{
|
||||
@@ -65,13 +125,33 @@ public sealed class SimulationStepTests
|
||||
[Fact]
|
||||
public void Constructor_RejectsDuplicatePlayers()
|
||||
{
|
||||
GameDefinition definition = new(ImmutableArray.Create(new(new(1), new(0, 0)), new PlayerDefinition(new(1), new(2, 3))));
|
||||
GameDefinition definition = new(new(new(new(0, 0), new(100, 100)), ImmutableArray<HazardDefinition>.Empty, ImmutableArray<TriggerDefinition>.Empty), ImmutableArray.Create(new(new(1), new(0, 0), 10), new PlayerDefinition(new(1), new(2, 3), 10)));
|
||||
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => new Simulation(definition, SimulationTestFactory.CreateConfig(), 11));
|
||||
|
||||
Assert.Contains("Duplicate player id 1", exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_RejectsSpawnOutsideBounds()
|
||||
{
|
||||
GameDefinition definition = new(new(new(new(0, 0), new(5, 5)), ImmutableArray<HazardDefinition>.Empty, ImmutableArray<TriggerDefinition>.Empty), ImmutableArray.Create(new PlayerDefinition(new(1), new(10, 20), 10)));
|
||||
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => new Simulation(definition, SimulationTestFactory.CreateConfig(), 11));
|
||||
|
||||
Assert.Contains("spawn must start inside world bounds", exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_RejectsNonPositiveHealth()
|
||||
{
|
||||
GameDefinition definition = new(new(new(new(0, 0), new(100, 100)), ImmutableArray<HazardDefinition>.Empty, ImmutableArray<TriggerDefinition>.Empty), ImmutableArray.Create(new PlayerDefinition(new(1), new(1, 1), 0)));
|
||||
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => new Simulation(definition, SimulationTestFactory.CreateConfig(), 11));
|
||||
|
||||
Assert.Contains("positive health", exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_RejectsNullDefinition()
|
||||
{
|
||||
|
||||
@@ -7,9 +7,9 @@ namespace SideScrollerGame.Sim.Tests;
|
||||
|
||||
internal static class SimulationTestFactory
|
||||
{
|
||||
public static GameDefinition CreateGameDefinition()
|
||||
public static GameDefinition CreateGameDefinition(AxisAlignedBounds? worldBounds = null, ImmutableArray<HazardDefinition> hazards = default, ImmutableArray<TriggerDefinition> triggers = default)
|
||||
{
|
||||
return new(ImmutableArray.Create(new PlayerDefinition(new(1), new(10, 20))));
|
||||
return new(new(worldBounds ?? new(new(0, 0), new(100, 100)), hazards, triggers), ImmutableArray.Create(new PlayerDefinition(new(1), new(10, 20), 10)));
|
||||
}
|
||||
|
||||
public static SimulationConfig CreateConfig(VerificationMode verificationMode = VerificationMode.None)
|
||||
|
||||
Reference in New Issue
Block a user