init
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
# حکمشو — بکاند (Go)
|
||||
|
||||
سرور بازی حکم. فازهای انجامشده: **اسکلت + احراز هویت OTP (کاوهنگار) + JWT**، **موتور حکم + لایه WebSocket (matchmaking + اجرای میز بهصورت goroutine)**، **مقاومسازی realtime (reconnect + timeout نوبت + بات)** و **اقتصاد و فروشگاه (کیفپول، اسکین کارت، سکه روزانه، تبلیغ rewarded، خرید IAP، تسویهی داخل بازی)**.
|
||||
|
||||
## اجرا (محلی)
|
||||
|
||||
```bash
|
||||
cp .env.example .env # و مقادیر را پر کنید
|
||||
# یا مستقیم با env:
|
||||
ADMIN_MOBILE=09120000000 ADMIN_OTP=11111 go run ./cmd/server
|
||||
```
|
||||
|
||||
سرور روی `:8080` بالا میآید.
|
||||
|
||||
> بهخاطر محدودیت شبکه برای دانلود ماژولها، اگر لازم شد از میرور استفاده کنید:
|
||||
> `go env -w GOPROXY=https://proxy.golang.org,direct` (همین الان کار کرد) یا یک میرور داخلی معتبر.
|
||||
|
||||
## متغیرهای محیطی
|
||||
|
||||
| متغیر | پیشفرض | توضیح |
|
||||
|-------|---------|-------|
|
||||
| `ADDR` | `:8080` | آدرس گوشدادن |
|
||||
| `DB_PATH` | `hakemsho.db` | مسیر فایل SQLite |
|
||||
| `JWT_SECRET` | `change-me…` | کلید امضای JWT (در پروداکشن عوض شود) |
|
||||
| `KAVE_API_KEY` | — | کلید کاوهنگار |
|
||||
| `KAVE_TEMPLATE` | `loginotp` | تمپلیت verify |
|
||||
| `ADMIN_MOBILE` / `ADMIN_OTP` | — | ورود تستی بدون SMS |
|
||||
|
||||
## endpointها (فاز ۱)
|
||||
|
||||
```
|
||||
GET /health
|
||||
POST /api/auth/login-otp { mobile, fcm_token? } → { message: "otp sent" }
|
||||
POST /api/auth/check-otp { mobile, token } → { user, token }
|
||||
GET /api/me (Bearer JWT) → { user }
|
||||
```
|
||||
|
||||
- throttle: حداکثر **۳ بار در دقیقه** روی `login-otp` به ازای هر شماره.
|
||||
- کد OTP **۵ رقمی**، اعتبار **۱۵ دقیقه**، پس از مصرف حذف میشود.
|
||||
- کاوهنگار از `verify/lookup.json` با تمپلیت `loginotp` استفاده میکند (مطابق پروژه approagency).
|
||||
|
||||
## WebSocket بازی (فاز ۲)
|
||||
|
||||
اتصال: `GET /ws?token=<JWT>` (یا هدر `Authorization: Bearer <JWT>`).
|
||||
|
||||
جریان کلی: اتصال → `join_queue` → وقتی ۴ نفر در صف شد میز ساخته میشود → `matched` + `state` → حاکم `choose_trump` → بازیکنان بهنوبت `play_card` → `hand_over` / `game_over`.
|
||||
|
||||
پیامهای **کلاینت → سرور** (JSON):
|
||||
```json
|
||||
{ "type": "join_queue", "mode": "normal" }
|
||||
{ "type": "choose_trump", "suit": "hearts" } // فقط حاکم
|
||||
{ "type": "play_card", "card": "AS" }
|
||||
{ "type": "leave" }
|
||||
```
|
||||
|
||||
پیامهای **سرور → کلاینت**:
|
||||
```json
|
||||
{ "type": "matched", "room": "r1", "seat": 0, "players": [...] }
|
||||
{ "type": "state", "phase": "playing", "your_seat": 0, "hakem": 0, "turn": 1,
|
||||
"trump": "hearts", "your_hand": ["AS","10H"], "hand_counts": [13,13,13,13],
|
||||
"trick": [{"seat":0,"card":"AS"}], "lead_suit": "spades",
|
||||
"tricks_won": [0,0], "scores": [0,0], "target_score": 7, "players": [...] }
|
||||
{ "type": "hand_over", "winner_team": 0, "kot": false, "points": 1, "scores": [1,0] }
|
||||
{ "type": "game_over", "winner_team": 0, "scores": [7,5] }
|
||||
{ "type": "player_disconnected", "seat": 2 } // قطع موقت؛ جایگاه برای بازگشت باز است
|
||||
{ "type": "player_reconnected", "seat": 2 }
|
||||
{ "type": "player_left", "seat": 2 } // خروج دائمی؛ جایگاه به بات تبدیل شد
|
||||
{ "type": "error", "message": "..." }
|
||||
```
|
||||
|
||||
در پیام `state`، هر بازیکن در `players` فیلدهای `bot` و `connected` دارد تا UI وضعیت میز را نشان دهد.
|
||||
|
||||
## مقاومسازی realtime (فاز ۵)
|
||||
|
||||
- **timeout نوبت**: اگر بازیکن در مهلت نوبت (پیشفرض ۲۰s) حرکت نکند، سرور یک حرکت مجاز خودکار میزند (انتخاب حکمِ پرتعدادترین خال؛ پایینترین کارتِ مجاز). همین مسیر، باتها و بازیکنانِ قطعشده را هم اداره میکند (با تأخیر کوتاهتر).
|
||||
- **reconnect**: قطع اتصال میز را خاتمه نمیدهد؛ جایگاه باز میماند و خودکار بازی میشود. کاربر با همان توکن دوباره وصل میشود (هاب نگاشت `userID → میز/جایگاه` نگه میدارد) و `matched`+`state` میگیرد. اگر هیچ انسانِ متصلی در میز نماند، میز بسته میشود.
|
||||
- **بات / پرکردن میز**: اگر تا مهلت matchmaking (پیشفرض ۱۲s) چهار انسان جمع نشد، جایگاههای خالی با بات پر میشوند تا بازی شروع شود. `leave` هم جایگاه را به بات تبدیل میکند تا بقیه ادامه دهند.
|
||||
- **ایمنی همزمانی**: `seatInfo` فقط متعلق به goroutine میز است؛ هاب پس از ساخت میز هرگز به آن دست نمیزند و همهچیز از طریق action/endInfo رد و بدل میشود. تایمرها با شمارندهی نسل (generation) از اجرای کهنه مصوناند. همهی تستها با `-race` سبزند.
|
||||
|
||||
نکات معماری:
|
||||
- هر **میز یک goroutine** است و تنها نویسندهی state بازی (الگوی actor، بدون قفل).
|
||||
- **هاب** تنها هماهنگکننده است (اتصالها، صف، مسیریابی) و تنها نویسندهی state خودش.
|
||||
- نمای هر بازیکن فقط **کارتهای خودش** را دارد؛ از بقیه فقط «تعداد کارت».
|
||||
- منبع حقیقت سرور است: نوبت، follow-suit و مالکیت کارت سمت سرور اعتبارسنجی میشوند.
|
||||
- تستها: بازی کامل ۴ نفره روی WebSocket واقعی و سناریوی قطعاتصال، هر دو با `-race`.
|
||||
|
||||
## اقتصاد و فروشگاه
|
||||
|
||||
endpointها (همه پشت JWT):
|
||||
```
|
||||
GET /api/wallet → سکه، بلیط، XP، سطح، جام، VIP، کارت انتخابی
|
||||
GET /api/shop → کاتالوگ + کارتهای متعلق به کاربر + کارت انتخابی
|
||||
POST /api/shop/buy-card { card_id } خرید اسکین کارت با سکه
|
||||
POST /api/shop/select-card { card_id } انتخاب اسکین
|
||||
POST /api/shop/purchase { store, kind, product_id, token } تأیید خرید IAP
|
||||
POST /api/rewards/daily سکه روزانه (هر ۲۴ ساعت)
|
||||
POST /api/rewards/ad { token } سکه رایگان پس از تبلیغ rewarded
|
||||
```
|
||||
|
||||
مدل اقتصادی (کاتالوگ در [internal/economy/catalog.go](internal/economy/catalog.go)، برگرفته از اپ مرجع):
|
||||
- **بستههای سکه** (پول واقعی، ۶ سطح با بونوس و VIP هدیه)، **بستههای بلیط**، **اسکین کارت** (با سکه)، **بوستر XP**، **انواع میز** (ورودی/جایزه/XP/جام).
|
||||
- **سکه روزانه** و **سکه رایگان ۵۰تایی** فقط با دیدن کامل تبلیغ (تأیید سمتسرور؛ ضد تکرار با token یکتا + سقف روزانه).
|
||||
- **VIP**: ۱۰٪ سکهی بیشتر در هر خرید.
|
||||
|
||||
پرداخت و تبلیغ پشت اینترفیساند (`AdVerifier`, `IAPVerifier` در [internal/economy/verify.go](internal/economy/verify.go)):
|
||||
- تبلیغ: **تپسل** (آداپتر آماده) · IAP: **کافهبازار و مایکت** (آداپتر آماده).
|
||||
- فعلاً تأییدکنندهی **توسعه** (`Dev*Verifier`) وصل است؛ با تنظیم creds به آداپتر واقعی سوییچ میشود.
|
||||
|
||||
### تسویهی داخل بازی
|
||||
`join_queue` یک `tier` میگیرد (مثل `beginner`/`pro`). هاب هنگام ورود به صف **ورودی** را کسر میکند؛ در پایان بازی **جایزه/XP/جام** به برندگانِ انسان واریز و بازی در `game_history` ثبت میشود. اگر بازی پیش از پایان لغو شود، ورودی **بازگردانده** میشود (بهجز کسی که داوطلبانه `leave` کرده). تسویه پشت اینترفیس `ws.Settler` است (لایه ws از سکه بیخبر میماند).
|
||||
|
||||
## ساخت باینری / Docker
|
||||
|
||||
```bash
|
||||
CGO_ENABLED=0 go build -ldflags="-s -w" -o server ./cmd/server # باینری استاتیک
|
||||
docker build -t hakemsho-backend . # image مینیمال (distroless)
|
||||
```
|
||||
|
||||
## ساختار
|
||||
|
||||
```
|
||||
cmd/server نقطه ورود و wiring
|
||||
internal/config خواندن env
|
||||
internal/store SQLite + migrations (embed)
|
||||
internal/user مدل و ریپوی کاربر
|
||||
internal/auth OTP، JWT، کاوهنگار، handlerها
|
||||
internal/httpx پاسخ JSON و throttle
|
||||
internal/game موتور حکم (کارت، قوانین، state machine) + تستها
|
||||
internal/ws هاب، میز (room)، کلاینت، پروتکل WebSocket، تسویه + تستها
|
||||
internal/economy کیفپول، کاتالوگ، کارت، روزانه/تبلیغ، IAP، تسویه + تستها
|
||||
```
|
||||
|
||||
## قدم بعدی
|
||||
|
||||
- وصلکردن آداپتر واقعی **تپسل** و **بازار/مایکت** با creds (الان استاب توسعه است).
|
||||
- شروع **فرانت Flutter/Flame** (لاگین/OTP → لابی/فروشگاه → میز بازی).
|
||||
- بهبود هوش بات (فعلاً حرکت مجاز ساده میزند).
|
||||
- به [../BACKEND_PLAN.md](../BACKEND_PLAN.md) رجوع کنید.
|
||||
Reference in New Issue
Block a user