2026-07-20 17:05:17 +08:00
|
|
|
using FluentAssertions;
|
|
|
|
|
using Game.TicTacToe;
|
|
|
|
|
using OECS;
|
2026-07-21 15:47:33 +08:00
|
|
|
using OECS.PlayTest;
|
2026-07-20 17:05:17 +08:00
|
|
|
using Xunit;
|
|
|
|
|
using Xunit.Abstractions;
|
|
|
|
|
|
|
|
|
|
namespace Game.TicTacToe.Tests;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Baseline snapshot and log tests for TicTacToe.
|
|
|
|
|
/// Captures the world state as text and R3 change logs so they can be
|
|
|
|
|
/// reviewed manually and compared across changes.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public class SnapshotTests
|
|
|
|
|
{
|
|
|
|
|
private readonly ITestOutputHelper _output;
|
|
|
|
|
|
|
|
|
|
public SnapshotTests(ITestOutputHelper output)
|
|
|
|
|
{
|
|
|
|
|
_output = output;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void Snapshot_InitialBoard()
|
|
|
|
|
{
|
|
|
|
|
var world = new World();
|
|
|
|
|
for (int r = 0; r < 3; r++)
|
|
|
|
|
for (int c = 0; c < 3; c++)
|
|
|
|
|
world.AddComponent(world.CreateEntity(), new Cell { Row = r, Col = c });
|
|
|
|
|
|
|
|
|
|
world.SetSingleton(new GameState
|
|
|
|
|
{
|
|
|
|
|
CurrentPlayer = Player.X,
|
|
|
|
|
Status = GameStatus.Playing,
|
|
|
|
|
MoveCount = 0
|
|
|
|
|
});
|
|
|
|
|
|
2026-07-21 15:47:33 +08:00
|
|
|
var snapshot = TestHelpers.SnapshotWorld(world);
|
2026-07-20 17:05:17 +08:00
|
|
|
_output.WriteLine(snapshot);
|
|
|
|
|
|
|
|
|
|
snapshot.Should().Contain("GameState: X's turn, 0 moves");
|
|
|
|
|
snapshot.Should().Contain("Cells: 9 total, 0 marked");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void Snapshot_AfterThreeMoves()
|
|
|
|
|
{
|
2026-07-21 15:47:33 +08:00
|
|
|
var (world, group) = TestHelpers.SetupGame();
|
2026-07-20 17:05:17 +08:00
|
|
|
|
2026-07-21 15:47:33 +08:00
|
|
|
TestHelpers.PlayMoves(world, group, (0, 0), (1, 1), (2, 2));
|
2026-07-20 17:05:17 +08:00
|
|
|
|
2026-07-21 15:47:33 +08:00
|
|
|
var snapshot = TestHelpers.SnapshotWorld(world);
|
2026-07-20 17:05:17 +08:00
|
|
|
_output.WriteLine(snapshot);
|
|
|
|
|
|
|
|
|
|
snapshot.Should().Contain("GameState: O's turn, 3 moves");
|
|
|
|
|
snapshot.Should().Contain("Cells: 9 total, 3 marked");
|
|
|
|
|
snapshot.Should().Contain("(0,0): X");
|
|
|
|
|
snapshot.Should().Contain("(1,1): O");
|
|
|
|
|
snapshot.Should().Contain("(2,2): X");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void Snapshot_XWins()
|
|
|
|
|
{
|
2026-07-21 15:47:33 +08:00
|
|
|
var (world, group) = TestHelpers.SetupGame();
|
2026-07-20 17:05:17 +08:00
|
|
|
|
2026-07-21 15:47:33 +08:00
|
|
|
TestHelpers.PlayMoves(world, group, (0, 0), (1, 0), (0, 1), (1, 1), (0, 2));
|
2026-07-20 17:05:17 +08:00
|
|
|
|
2026-07-21 15:47:33 +08:00
|
|
|
var snapshot = TestHelpers.SnapshotWorld(world);
|
2026-07-20 17:05:17 +08:00
|
|
|
_output.WriteLine(snapshot);
|
|
|
|
|
|
|
|
|
|
snapshot.Should().Contain("GameState: XWon");
|
|
|
|
|
snapshot.Should().Contain("Cells: 9 total, 5 marked");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void Snapshot_Draw()
|
|
|
|
|
{
|
2026-07-21 15:47:33 +08:00
|
|
|
var (world, group) = TestHelpers.SetupGame();
|
2026-07-20 17:05:17 +08:00
|
|
|
|
2026-07-21 15:47:33 +08:00
|
|
|
TestHelpers.PlayMoves(world, group,
|
2026-07-20 17:05:17 +08:00
|
|
|
(0, 0), (0, 1), (0, 2),
|
|
|
|
|
(1, 1), (1, 0), (1, 2),
|
|
|
|
|
(2, 1), (2, 0), (2, 2));
|
|
|
|
|
|
2026-07-21 15:47:33 +08:00
|
|
|
var snapshot = TestHelpers.SnapshotWorld(world);
|
2026-07-20 17:05:17 +08:00
|
|
|
_output.WriteLine(snapshot);
|
|
|
|
|
|
|
|
|
|
snapshot.Should().Contain("GameState: Draw");
|
|
|
|
|
snapshot.Should().Contain("Cells: 9 total, 9 marked");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void Log_ReactivityDuringGame()
|
|
|
|
|
{
|
2026-07-21 15:47:33 +08:00
|
|
|
var (world, group) = TestHelpers.SetupGame();
|
2026-07-20 17:05:17 +08:00
|
|
|
|
2026-07-21 15:47:33 +08:00
|
|
|
using var capture = new ObservableCapture(world);
|
|
|
|
|
capture.FormatWith<Mark>(m => m.Player.ToString());
|
|
|
|
|
capture.FormatWith<GameState>(s => $"{s.Status} ({s.CurrentPlayer}, {s.MoveCount} moves)");
|
2026-07-20 17:05:17 +08:00
|
|
|
|
2026-07-21 15:47:33 +08:00
|
|
|
TestHelpers.PlayMoves(world, group, (0, 0), (1, 0), (0, 1), (1, 1), (0, 2));
|
2026-07-20 17:05:17 +08:00
|
|
|
|
2026-07-21 15:47:33 +08:00
|
|
|
var logText = string.Join("\n", capture.GetLogLines());
|
2026-07-20 17:05:17 +08:00
|
|
|
_output.WriteLine(logText);
|
|
|
|
|
|
2026-07-21 15:47:33 +08:00
|
|
|
capture.GetLogLines().Should().Contain(l => l.Contains("ComponentAdded") && l.Contains("Mark"));
|
|
|
|
|
capture.GetLogLines().Should().Contain(l => l.Contains("ComponentModified") && l.Contains("GameState"));
|
2026-07-20 17:05:17 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void Serialization_RoundTrip_PreservesSnapshot()
|
|
|
|
|
{
|
2026-07-21 15:47:33 +08:00
|
|
|
var (world, group) = TestHelpers.SetupGame();
|
2026-07-20 17:05:17 +08:00
|
|
|
|
2026-07-21 15:47:33 +08:00
|
|
|
TestHelpers.PlayMoves(world, group, (0, 0), (1, 1), (2, 2));
|
2026-07-20 17:05:17 +08:00
|
|
|
|
2026-07-21 15:47:33 +08:00
|
|
|
var before = TestHelpers.SnapshotWorld(world);
|
2026-07-20 17:05:17 +08:00
|
|
|
|
|
|
|
|
using var stream = new MemoryStream();
|
|
|
|
|
WorldSerializer.Save(world, stream);
|
|
|
|
|
stream.Position = 0;
|
|
|
|
|
|
|
|
|
|
var world2 = new World();
|
|
|
|
|
WorldSerializer.Load(world2, stream);
|
|
|
|
|
|
2026-07-21 15:47:33 +08:00
|
|
|
var after = TestHelpers.SnapshotWorld(world2);
|
2026-07-20 17:05:17 +08:00
|
|
|
|
|
|
|
|
_output.WriteLine("=== Before ===");
|
|
|
|
|
_output.WriteLine(before);
|
|
|
|
|
_output.WriteLine("=== After ===");
|
|
|
|
|
_output.WriteLine(after);
|
|
|
|
|
|
|
|
|
|
after.Should().Be(before);
|
|
|
|
|
}
|
2026-07-21 15:47:33 +08:00
|
|
|
}
|