diff --git a/cmd/server/settler.go b/cmd/server/settler.go index 3fb4eb9..77eaebb 100644 --- a/cmd/server/settler.go +++ b/cmd/server/settler.go @@ -82,6 +82,11 @@ func (g gameSettler) PlayerProfile(userID int64) (int64, string) { return p.Coins, p.RankTier } +// ChargeTicket یک بلیت برای بُرِ مجدد مصرف می‌کند (خطا اگر بلیت کافی نباشد). +func (g gameSettler) ChargeTicket(userID int64) error { + return g.eco.Adjust(context.Background(), userID, economy.CurrencyTicket, -1, "reshuffle", "") +} + // ChargePrivateTable یک میز خصوصیِ رایگان را برای کاربر مصرف می‌کند. func (g gameSettler) ChargePrivateTable(userID int64) error { return g.eco.ConsumePrivateTable(context.Background(), userID) diff --git a/internal/game/hokm.go b/internal/game/hokm.go index 556aa2e..6da7d6c 100644 --- a/internal/game/hokm.go +++ b/internal/game/hokm.go @@ -124,6 +124,19 @@ func (g *Game) startHand() { g.TrickDone = false } +// Reshuffle دستِ جاری را پیش از انتخابِ حکم دوباره بُر می‌زند و ۵ کارتِ تازه به +// همان حاکم می‌دهد (مصرفِ یک بلیت در لایه‌ی بالاتر انجام می‌شود). +func (g *Game) Reshuffle(seat int) error { + if g.Phase != PhaseChooseTrump { + return ErrWrongPhase + } + if seat != g.Hakem { + return ErrNotHakem + } + g.startHand() // همان حاکم، دستِ نو، باز هم منتظرِ انتخابِ حکم + return nil +} + // ChooseTrump حکم را توسط حاکم تعیین کرده و بقیه کارت‌ها را پخش می‌کند. func (g *Game) ChooseTrump(seat int, s Suit) error { if g.Phase != PhaseChooseTrump { diff --git a/internal/ws/hub.go b/internal/ws/hub.go index 06b10d6..1f5c8ce 100644 --- a/internal/ws/hub.go +++ b/internal/ws/hub.go @@ -173,7 +173,7 @@ func (h *Hub) handle(in inbound) { } h.enqueue(c, tier) } - case "choose_trump", "play_card", "leave": + case "choose_trump", "play_card", "reshuffle", "leave": if c.room != nil { if in.msg.Type == "leave" { delete(h.locations, c.UserID) // پس از خروج دائمی، reconnect نشود diff --git a/internal/ws/room.go b/internal/ws/room.go index 8192b0f..8ac66d0 100644 --- a/internal/ws/room.go +++ b/internal/ws/room.go @@ -262,6 +262,25 @@ func (r *Room) handleInput(act roomAction) { } r.graceUntil = time.Now().Add(r.dealDelay) // انیمیشنِ پخشِ بقیه‌ی کارت‌ها r.react() + case "reshuffle": + // بُرِ مجدد با مصرفِ یک بلیت: فقط حاکم و فقط پیش از انتخابِ حکم. + if r.game.Phase != game.PhaseChooseTrump || act.seat != r.game.Hakem { + r.sendError(act.seat, "بُر مجدد مجاز نیست") + return + } + if s.isBot || s.userID == 0 { + return + } + if err := r.settler.ChargeTicket(s.userID); err != nil { + r.sendError(act.seat, "بلیت کافی ندارید") + return + } + if err := r.game.Reshuffle(act.seat); err != nil { + r.sendError(act.seat, err.Error()) + return + } + r.graceUntil = time.Now().Add(r.firstHandDelay) // انیمیشنِ بُر زدنِ مجدد + r.react() case "play_card": card, err := game.ParseCard(act.msg.Card) if err != nil { diff --git a/internal/ws/settler.go b/internal/ws/settler.go index 6087d7f..45cb8c3 100644 --- a/internal/ws/settler.go +++ b/internal/ws/settler.go @@ -14,6 +14,8 @@ type Settler interface { RecordGameResult(userID int64, won bool) // پایان بازی (برد/باخت) // PlayerProfile سکه و نشانِ رتبه‌ی بازیکن را برای نمایش سرِ میز برمی‌گرداند. PlayerProfile(userID int64) (coins int64, rankTier string) + // ChargeTicket یک بلیت برای بُرِ مجدد در فازِ انتخابِ حکم مصرف می‌کند. + ChargeTicket(userID int64) error // میز خصوصی (دورهمی). ChargePrivateTable(userID int64) error // مصرفِ یک میز رایگان (خطا اگر سقف پر باشد) PrivateTableInfo(userID int64) (remaining int, unlimited bool) @@ -33,6 +35,7 @@ func (noopSettler) RecordHand(int64, bool, bool, bool) {} func (noopSettler) RecordCut(int64) {} func (noopSettler) RecordGameResult(int64, bool) {} func (noopSettler) PlayerProfile(int64) (int64, string) { return 0, "" } +func (noopSettler) ChargeTicket(int64) error { return nil } func (noopSettler) ChargePrivateTable(int64) error { return nil } func (noopSettler) PrivateTableInfo(int64) (int, bool) { return 0, true } func (noopSettler) TargetHands(string) int { return 7 } diff --git a/internal/ws/ws_test.go b/internal/ws/ws_test.go index 401325c..a75ea90 100644 --- a/internal/ws/ws_test.go +++ b/internal/ws/ws_test.go @@ -480,6 +480,7 @@ func (s *recordingSettler) RecordGameResult(int64, bool) { } func (s *recordingSettler) PlayerProfile(int64) (int64, string) { return 0, "" } +func (s *recordingSettler) ChargeTicket(int64) error { return nil } func (s *recordingSettler) ChargePrivateTable(int64) error { return nil } func (s *recordingSettler) PrivateTableInfo(int64) (int, bool) { return 5, false }