# حکم‌شو — بک‌اند (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=` (یا هدر `Authorization: Bearer `). جریان کلی: اتصال → `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) رجوع کنید.