using System.Collections.Generic; using Xunit; using XWorld.Framework; using XWorld.Server.Host; using RPS.Core; namespace XWorld.Server.Host.Tests { /// /// Proves "framework zero-change loads a new game via ALC": loads RPS through the existing ALC contract. /// Loads RPS from TestGames/rps/1, drives it with 1 human + 1 AI seat, advances past 60s, /// asserts broadcasts decode and game progresses to Finished. /// public class RpsModuleLoadTests { private sealed class FakeOutput : IRoomOutput { public readonly List Broadcasts = new List(); public void Broadcast(NetMessage m) => Broadcasts.Add(m); public void SendTo(int playerId, NetMessage m) { } } private static readonly IReadOnlyList Players = new[] { new PlayerInfo { PlayerId = 1, Name = "Human", IsAI = false }, new PlayerInfo { PlayerId = -1, Name = "AI", IsAI = true }, }; [Fact] public void Load_Rps_v1_Via_ALC_ProducesRoom_ThatImplementsSharedInterface() { var module = new GameModuleLoader().Load(TestGames.Root, "rps", 1); Assert.Equal("rps", module.GameId); Assert.Equal(1, module.Version); IGameServerRoom room = module.CreateRoom(); Assert.NotNull(room); // Verify ALC isolation: the implementation type lives in the game ALC, // not the default context where IGameServerRoom is defined. Assert.NotEqual(typeof(IGameServerRoom).Assembly, room.GetType().Assembly); } [Fact] public void Load_Rps_v1_Via_ALC_BroadcastsSnapshots_AndProgressesToFinished() { var module = new GameModuleLoader().Load(TestGames.Root, "rps", 1); IGameServerRoom room = module.CreateRoom(); var output = new FakeOutput(); bool ended = false; var ctx = new RoomCtx( new DeterministicRandom(99), new ManualClock(), new ConsoleLogger(), new InMemoryStorage(), output, _ => ended = true); room.OnRoomStart(Players, ctx); // Initial broadcast on start Assert.True(output.Broadcasts.Count >= 1); // Decode the first snapshot to confirm Round=1, Phase=Choosing var logic = new RpsLogic(); var first = logic.Decode(output.Broadcasts[0].Payload); Assert.Equal(1, first.Round); Assert.Equal(RpsPhase.Choosing, first.Phase); // Drive with large dt to tick through all rounds within 60s total: // Each tick dt=10f: 10+10+10+10+10+10 = 60s → should finish by tick 6 for (int i = 0; i < 10 && !ended; i++) room.OnTick(10f); Assert.True(ended, "EndRoom must have been called (game finished within 10 large ticks)"); Assert.True(output.Broadcasts.Count > 1, "Should have multiple broadcasts"); // Decode the last snapshot — must be Finished var last = output.Broadcasts[output.Broadcasts.Count - 1]; var finalState = logic.Decode(last.Payload); Assert.Equal(RpsPhase.Finished, finalState.Phase); // Winner is 0, 1, or 2 (draw) — must be a valid value Assert.True(finalState.Winner >= 0 && finalState.Winner <= 2, $"Winner should be 0, 1, or 2 but was {finalState.Winner}"); } } }