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 Broadcasts = new List(); 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 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)出 Rock:opcode=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( () => 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); // 第一大 tick:TotalElapsed=10 < 60,不结束 Assert.Empty(first); var ended = new List(); 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" } } }