The server only sent the queue size to the player who just joined, and the client dropped the count entirely (emitMM ignored s.players). So two friends queuing together never saw each other, even though the server does seat 2+ waiting humans together within ~25s. - Server: BroadcastQueueLocked() pushes the current queue size to EVERY waiting player on join/cancel (not just the joiner). - Client: thread the count through emitMM → MatchmakingState.waiting. - MatchmakingScreen shows "N players in queue" (mm.inQueue) when ≥2 humans wait, so friends can tell they're queued together before bots fill the empty seats. 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.
Persistence (EF Core)
AppDbContext stores each profile as a JSON blob (ProfileRow) + a coin
LedgerRow audit trail. Provider is config-driven:
// appsettings.json
"Database": { "Provider": "sqlite" }, // or "postgres"
"ConnectionStrings": { "Default": "Data Source=hokm.db" }
// Postgres (Supabase): Provider="postgres",
// Default="Host=...;Database=...;Username=...;Password=...;SSL Mode=Require"
Schema is created at startup via EnsureCreated() (swap to EF migrations for prod).
Server-authoritative endpoints (JWT):
GET /api/profile·PUT /api/profile(displayName/avatar/title/cardFront/cardBack)POST /api/profile/plan(upgrade to pro)GET /api/coins/packs·POST /api/coins/buy{ packId }(credits + ledger; real Zarinpal/IDPay TODO)POST /api/match/result{ MatchSummary }→ computes rewards viaProfiles/Gamification.cs(C# port ofsrc/lib/online/gamification.ts), updates profile + ledger, returnsRewardResult
Auth (/api/auth/...) upserts the profile on first sign-in.
TODO
- Wire the Next client (
SignalrService) to these endpoints for profile/coins/match (currently still mock-backed to avoid a half-migrated economy) - EF migrations; Postgres (Supabase) connection for prod
- JWT issued by the V2 Identity Service; phone OTP via Kavenegar/SMS.ir
- Private rooms + friend invites over the hub; server-side ranked entry deduction at match start