- Daily reward now routes through the global CelebrationOverlay: new "daily"
variant + coins count-up; claiming closes the daily modal and fires
celebrate({variant:"daily", coins}). Unifies the "you earned X" moment.
- Premium (pro) gold chat is now visible to the OTHER player: ChatMessage gains
senderPro; server resolves each participant's plan once (SocialService.IsPro)
and stamps it on ChatMessageDto; ChatScreen styles incoming bubbles with
.premium-chat when senderPro. Mock marks ~half its friends pro so it's visible
offline too.
Verified: tsc + next build + dotnet build all pass.
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