Initial commit: Client Doc docs Server Tools

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
ud18010
2026-07-10 10:24:29 +08:00
co-authored by Cursor
commit 7e35d8da31
3374 changed files with 680813 additions and 0 deletions
+110
View File
@@ -0,0 +1,110 @@
using System.Collections.Generic;
using Xunit;
using XWorld.Framework;
using XWorld.Framework.Protocol;
using XWorld.Server.Host;
using RPS.Core;
namespace XWorld.Server.Host.Tests
{
public class RoomHostTests
{
private sealed class Capture : IRoomOutput
{
public List<NetMessage> Broadcasts = new List<NetMessage>();
public void Broadcast(NetMessage m) => Broadcasts.Add(m);
public void SendTo(int playerId, NetMessage m) { }
}
// 1 真人(seat 0) + 1 AI(seat 1)
private static readonly IReadOnlyList<PlayerInfo> Players = new[]
{
new PlayerInfo { PlayerId = 1, Name = "H", IsAI = false },
new PlayerInfo { PlayerId = -1, Name = "AI", IsAI = true },
};
private static RpsState LastState(Capture cap)
=> new RpsLogic().Decode(cap.Broadcasts[cap.Broadcasts.Count - 1].Payload);
private static RoomHost NewHost()
=> new RoomHost(new GameModuleRegistry(new GameModuleLoader(), TestGames.Root), new ConsoleLogger());
private static RoomConfig Cfg()
=> new RoomConfig { GameId = "rps", Version = 1, Seed = 1, PlayerCount = 2 };
[Fact]
public void CreateRoom_TickToEnd_ReleasesModule()
{
var host = NewHost();
var cap = new Capture();
var room = host.CreateRoom("A", "rps", 1, Cfg(), Players, cap);
Assert.Equal(1, host.ActiveRoomCount);
// 大 dt 推进:60s 对局在数个大 tick 内结束
for (int i = 0; i < 10 && host.ActiveRoomCount > 0; i++) host.TickAll(10f);
Assert.Equal(0, host.ActiveRoomCount);
Assert.False(room.IsActive);
Assert.Equal(RpsPhase.Finished, LastState(cap).Phase);
}
[Fact]
public void Deliver_RoutesChoice_AffectsState()
{
var host = NewHost();
var cap = new Capture();
host.CreateRoom("A", "rps", 1, Cfg(), Players, cap);
// seat 0(玩家 1)出 Rockopcode=1(ChoiceOpcode)payload=单字节 choice
var w = new PacketWriter();
w.WriteByte((byte)Choice.Rock);
host.DeliverTo("A", 1, new NetMessage(1, w.ToArray()));
host.TickAll(0.1f); // 小 dt,仍在 Choosing,广播快照
Assert.Equal(Choice.Rock, LastState(cap).Choices[0]);
}
[Fact]
public void CreateRoom_DuplicateRoomId_Throws()
{
var host = NewHost();
host.CreateRoom("dup", "rps", 1, Cfg(), Players, new Capture());
Assert.Throws<System.InvalidOperationException>(
() => host.CreateRoom("dup", "rps", 1, Cfg(), Players, new Capture()));
}
[Fact]
public void TickAll_ReturnsEndedRoomIds()
{
var host = NewHost();
host.CreateRoom("E", "rps", 1, Cfg(), Players, new Capture());
var first = host.TickAll(10f); // 第一大 tickTotalElapsed=10 < 60,不结束
Assert.Empty(first);
var ended = new List<EndedRoom>();
for (int i = 0; i < 10 && !ended.Exists(e => e.RoomId == "E"); i++)
ended.AddRange(host.TickAll(10f));
Assert.Contains(ended, e => e.RoomId == "E"); // 累计超过 60s 后该 tick 返回结束房间
}
[Fact]
public void TickAll_EndedRoom_CarriesResult()
{
var host = NewHost();
host.CreateRoom("R", "rps", 1, Cfg(), Players, new Capture());
RoomEndResult result = null;
bool found = false;
for (int i = 0; i < 10 && !found; i++)
foreach (var e in host.TickAll(10f))
if (e.RoomId == "R") { found = true; result = e.Result; }
Assert.True(found);
Assert.NotNull(result); // 夹具已重建,RPS 走 EndRoom(result)
Assert.NotNull(result.ResultBlob);
Assert.NotEmpty(result.ResultBlob); // "最终比分 x : y" 或 "平局 x : y"
}
}
}