Phase G: scaffold .NET 10 + SignalR backend (engine port + hub + auth)

- server/ monorepo: Hokm.Engine (C# port of TS engine+AI, validated by sim),
  Hokm.Server (SignalR GameHub, in-memory matchmaking/rooms, server-side turn
  timers + bot fill + disconnect handling, per-seat state broadcast), Hokm.Sim
- JWT dev auth (OTP 1234 + email); CORS for the Next client; /hub/game
- NuGet restored from mirrors (Soroush Nexus + Liara); NuGetAudit off
- README + .NET .gitignore; static class Engine renamed Rules (namespace clash)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-04 12:42:15 +03:30
parent ae239f4c51
commit aaf66b921f
22 changed files with 1220 additions and 0 deletions
+43
View File
@@ -0,0 +1,43 @@
using Hokm.Server.Game;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;
namespace Hokm.Server.Hubs;
public record MatchmakeRequest(string Name, string Avatar, int Level, string Plan);
[Authorize]
public sealed class GameHub : Hub
{
private readonly GameManager _manager;
public GameHub(GameManager manager) => _manager = manager;
private string Uid => Context.UserIdentifier ?? Context.ConnectionId;
public override Task OnConnectedAsync()
{
_manager.OnConnected(Uid);
return base.OnConnectedAsync();
}
public override Task OnDisconnectedAsync(Exception? exception)
{
_manager.OnDisconnected(Uid);
return base.OnDisconnectedAsync(exception);
}
public void StartMatchmaking(MatchmakeRequest req) =>
_manager.StartMatchmaking(new Player
{
UserId = Uid,
Name = string.IsNullOrWhiteSpace(req.Name) ? "بازیکن" : req.Name,
Avatar = req.Avatar,
Level = req.Level,
Plan = req.Plan,
});
public void CancelMatchmaking() => _manager.CancelMatchmaking(Uid);
public void PlayCard(string cardId) => _manager.PlayCard(Uid, cardId);
public void ChooseTrump(string suit) => _manager.ChooseTrump(Uid, suit);
public void SendReaction(string reaction) => _manager.SendReaction(Uid, reaction);
}