using System.Numerics; using System.Text.RegularExpressions; using Microsoft.AspNetCore.SignalR; namespace PongGame.Hubs; public class PongLobby { private readonly HashSet connectedPlayers = new(); private readonly Dictionary PongRooms = new(); public PongLobby(ILogger logger) => Logger = logger; public const int ROOM_ID_LENGTH = 4; public PongPlayer CreatePlayer() { var player = new PongPlayer(); lock (connectedPlayers) connectedPlayers.Add(player); return player; } public void RemovePlayer(PongPlayer player) { if (player.ConnectedRoom is PongRoom room) room.Leave(player); lock (connectedPlayers) _ = connectedPlayers.Remove(player); } public PongRoom CreateRoom(PongPlayer player) { PongRoom room; lock (PongRooms) { room = new(GenerateRoomId(), Logger); PongRooms.Add(room.ID, room); } room.Join(player); return room; } public Task JoinRoom(PongPlayer player, string roomId) { PongRoom? room; lock (PongRooms) { room = PongRooms.GetValueOrDefault(roomId); } if (room is null) throw new HubException($"Room [{roomId}] not found!"); room.Join(player); return Task.FromResult(room); } public Task LeaveRoom(PongPlayer player) { if (player.ConnectedRoom is PongRoom room) { room.Leave(player); if (room.IsEmpty) lock (PongRooms) _ = PongRooms.Remove(room.ID); } return Task.CompletedTask; } private readonly Random random = new(); private readonly ILogger Logger; private const string ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; private string GenerateRoomId() { string id; do { id = string.Concat(Enumerable.Range(0, ROOM_ID_LENGTH).Select(_ => ALPHABET[random.Next(ALPHABET.Length)])); } while (PongRooms.ContainsKey(id)); return id; } public async Task ChangeUsername(PongPlayer player, string username) { username = ValidateUsername(username); if (player.Username == username) { await player.Client.UsernameChanged(username); return; } lock (connectedPlayers) { // TODO: separate hashset for usernames if (connectedPlayers.Select(i => i.Username).Contains(username)) throw new HubException($"Username {username} is already taken!"); } Logger.LogInformation("Player {Player} requested username change to [{username}]", player, username); player.Username = username; } private static readonly Regex UsernameRegex = new(@"^(?!.*[._ -]{2})[\w._ -]{3,20}$", RegexOptions.Compiled, TimeSpan.FromMilliseconds(200)); private static string ValidateUsername(string username) { username = username.Trim(); if (!UsernameRegex.IsMatch(username)) throw new HubException($"At most 20 characters, no two consecutive symbols"); return username; } }