Phase G: scaffold .NET 10 + SignalR backend (engine port + hub + auth)

- 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>
This commit is contained in:
soroush.asadi
2026-06-04 12:42:15 +03:30
parent ae239f4c51
commit aaf66b921f
22 changed files with 1220 additions and 0 deletions
+68
View File
@@ -0,0 +1,68 @@
# 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=<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`.
## 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)