- 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>
Hokm — .NET 10 + SignalR backend
Authoritative realtime server for the Hokm game. The TypeScript engine
(../src/lib/hokm) is ported to C# here so the server is the source of truth.
Projects
| Project | What |
|---|---|
src/Hokm.Engine |
Pure C# rules + AI (port of src/lib/hokm) — deal, hakem, trump, tricks, scoring, Kot, bot |
src/Hokm.Server |
ASP.NET Core + SignalR GameHub, in-memory matchmaking/rooms, JWT auth |
tools/Hokm.Sim |
All-AI simulation that validates the engine port |
NuGet sources
NuGet.config restores only from the configured mirrors (no nuget.org):
https://mirror.soroushasadi.com/repository/nuget-group/index.json(Nexus)https://package-mirror.liara.ir/repository/nuget/index.json(Liara)
Directory.Build.props disables NuGet audit (avoids reaching api.nuget.org).
Run
cd server
dotnet run --project tools/Hokm.Sim -c Release # validate the engine
dotnet run --project src/Hokm.Server -c Release # → http://localhost:5005
API
Dev auth (replace with the V2 Identity Service + Kavenegar/SMS.ir later):
POST /api/auth/otp/request{ phone }→{ devCode: "1234" }POST /api/auth/otp/verify{ phone, code, name? }→{ token, userId, name }(code1234)POST /api/auth/email{ email, password, name? }→{ token, userId, name }
SignalR hub: /hub/game (JWT required; pass ?access_token=<jwt>).
Client → server methods:
StartMatchmaking({ name, avatar, level, plan })— pro skips the queueCancelMatchmaking()ChooseTrump(suit)·PlayCard(cardId)·SendReaction(reaction)
Server → client events:
matchmaking{ phase, players, queuePosition }matchFound{ roomId, seat }stateGameStateDto(per-seat: only your own hand is included; others are counts)reaction{ seat, reaction }
The server runs server-side turn timers (20s → AI auto-plays), fills empty seats with bots, drives bot turns, and handles disconnect (the seat is marked offline and the timer auto-plays until they reconnect).
Wiring the Next.js client (next step)
Implement SignalrService in ../src/lib/online/signalr-service.ts against the
existing OnlineService interface (@microsoft/signalr), then switch
getService() in ../src/lib/online/service.ts from the mock to it. The hub's
GameStateDto is shaped to map directly onto the client GameState.
TODO
- EF Core + Postgres persistence (profiles, coins, rank, cosmetics, match history)
- JWT issued by the V2 Identity Service; phone OTP via Kavenegar/SMS.ir
- Private rooms + friend invites over the hub (engine/room already support 4 seats)
- Server-side reward calculation (currently client/profile-side)