Initial commit: Client Doc docs Server Tools
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
using System.Collections.Generic;
|
||||
using Xunit;
|
||||
using XWorld.Framework;
|
||||
using XWorld.Server.Host;
|
||||
using RPS.Core;
|
||||
|
||||
namespace XWorld.Server.Host.Tests
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public class RpsModuleLoadTests
|
||||
{
|
||||
private sealed class FakeOutput : IRoomOutput
|
||||
{
|
||||
public readonly List<NetMessage> Broadcasts = new List<NetMessage>();
|
||||
public void Broadcast(NetMessage m) => Broadcasts.Add(m);
|
||||
public void SendTo(int playerId, NetMessage m) { }
|
||||
}
|
||||
|
||||
private static readonly IReadOnlyList<PlayerInfo> 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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user