using System.Collections.Generic; namespace XWorld.Server.Gateway { public sealed class Matchmaker { public sealed class Seat { public int PlayerId; public bool IsAi; } public sealed class Match { public string GameId; public int Version; public List Seats = new List(); } private sealed class Waiter { public int Pid; public long EnqueuedTick; } private sealed class Bucket { public int PlayerCount; public List Waiters = new List(); } private readonly long _timeoutTicks; private int _aiSeq; private readonly Dictionary _buckets = new Dictionary(); public Matchmaker(long timeoutTicks) { _timeoutTicks = timeoutTicks; } private static string Key(string g, int v) => $"{g}@{v}"; public void Enqueue(int pid, string gameId, int version, int playerCount, long tick) { string k = Key(gameId, version); if (!_buckets.TryGetValue(k, out var b)) { b = new Bucket { PlayerCount = playerCount }; _buckets[k] = b; } b.Waiters.Add(new Waiter { Pid = pid, EnqueuedTick = tick }); } // 从等待队列移除某玩家(断线时调用);返回是否移除 public bool Remove(int pid) { foreach (var kv in _buckets) { var ws = kv.Value.Waiters; for (int i = 0; i < ws.Count; i++) if (ws[i].Pid == pid) { ws.RemoveAt(i); return true; } } return false; } // 返回本次可成形的对局(凑齐或超时 AI 补足) public IReadOnlyList Poll(long currentTick) { List formed = null; foreach (var kv in _buckets) { var b = kv.Value; string[] gv = kv.Key.Split('@'); while (b.Waiters.Count > 0 && (b.Waiters.Count >= b.PlayerCount || currentTick - b.Waiters[0].EnqueuedTick >= _timeoutTicks)) { var m = new Match { GameId = gv[0], Version = int.Parse(gv[1]) }; int take = b.Waiters.Count >= b.PlayerCount ? b.PlayerCount : b.Waiters.Count; for (int i = 0; i < take; i++) m.Seats.Add(new Seat { PlayerId = b.Waiters[i].Pid, IsAi = false }); b.Waiters.RemoveRange(0, take); while (m.Seats.Count < b.PlayerCount) m.Seats.Add(new Seat { PlayerId = -(++_aiSeq), IsAi = true }); // AI 用负 pid (formed ??= new List()).Add(m); } } return formed ?? (IReadOnlyList)System.Array.Empty(); } } }