# 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 ```bash 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=`). 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: ```jsonc // 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