Files
HokmPlay/server
soroush.asadi 940e2af6d2
CI/CD / CI - API (dotnet build + engine sim) (push) Successful in 1m24s
CI/CD / CI - Web (tsc + next build) (push) Successful in 1m10s
CI/CD / Deploy - local stack (db + server + web) (push) Successful in 1m52s
feat(online): live queue count — friends see each other waiting
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>
2026-06-19 19:26:13 +03:30
..

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 } (code 1234)
  • 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 queue
  • CancelMatchmaking()
  • ChooseTrump(suit) · PlayCard(cardId) · SendReaction(reaction)

Server → client events:

  • matchmaking { phase, players, queuePosition }
  • matchFound { roomId, seat }
  • state GameStateDto (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 via Profiles/Gamification.cs (C# port of src/lib/online/gamification.ts), updates profile + ledger, returns RewardResult

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