feat: add coin for private table
This commit is contained in:
@@ -87,6 +87,24 @@ func (g gameSettler) ChargeTicket(userID int64) error {
|
|||||||
return g.eco.Adjust(context.Background(), userID, economy.CurrencyTicket, -1, "reshuffle", "")
|
return g.eco.Adjust(context.Background(), userID, economy.CurrencyTicket, -1, "reshuffle", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ChargeCoins سکهٔ شرطِ میزِ خصوصی را کسر میکند (خطا اگر موجودی کافی نباشد).
|
||||||
|
func (g gameSettler) ChargeCoins(userID int64, amount int64, ref string) error {
|
||||||
|
if amount <= 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return g.eco.Adjust(context.Background(), userID, economy.CurrencyCoin, -amount, "private_stake", ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AwardCoins سهمِ برنده از potِ میزِ خصوصی را اضافه میکند.
|
||||||
|
func (g gameSettler) AwardCoins(userID int64, amount int64, ref string) {
|
||||||
|
if amount <= 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := g.eco.Adjust(context.Background(), userID, economy.CurrencyCoin, amount, "private_pot", ref); err != nil {
|
||||||
|
slog.Error("award coins", "user", userID, "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ChargePrivateTable یک میز خصوصیِ رایگان را برای کاربر مصرف میکند.
|
// ChargePrivateTable یک میز خصوصیِ رایگان را برای کاربر مصرف میکند.
|
||||||
func (g gameSettler) ChargePrivateTable(userID int64) error {
|
func (g gameSettler) ChargePrivateTable(userID int64) error {
|
||||||
return g.eco.ConsumePrivateTable(context.Background(), userID)
|
return g.eco.ConsumePrivateTable(context.Background(), userID)
|
||||||
|
|||||||
+26
-3
@@ -50,6 +50,7 @@ type pendingTable struct {
|
|||||||
clients []*Client
|
clients []*Client
|
||||||
hands int // تعداد دستِ انتخابشده توسط میزبان (۳/۵/۷)
|
hands int // تعداد دستِ انتخابشده توسط میزبان (۳/۵/۷)
|
||||||
rot int // چرخشِ جایگاههای غیرِمیزبان (۰..۲)
|
rot int // چرخشِ جایگاههای غیرِمیزبان (۰..۲)
|
||||||
|
stake int // شرطِ سکهایِ هر بازیکن (۰ یعنی رایگان)
|
||||||
started bool // پس از فشردن «شروع» توسط میزبان
|
started bool // پس از فشردن «شروع» توسط میزبان
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,7 +189,9 @@ func (h *Hub) handle(in inbound) {
|
|||||||
case "join_table":
|
case "join_table":
|
||||||
h.joinTable(c, in.msg.Code)
|
h.joinTable(c, in.msg.Code)
|
||||||
case "start_table":
|
case "start_table":
|
||||||
h.startTable(c, in.msg.Hands)
|
h.startTable(c, in.msg.Hands, in.msg.Stake)
|
||||||
|
case "set_stake":
|
||||||
|
h.setStake(c, in.msg.Stake)
|
||||||
case "rotate_table":
|
case "rotate_table":
|
||||||
h.rotateTable(c, in.msg.Dir)
|
h.rotateTable(c, in.msg.Dir)
|
||||||
case "leave_table":
|
case "leave_table":
|
||||||
@@ -245,12 +248,15 @@ func (h *Hub) joinTable(c *Client, code string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// startTable شمارش معکوس را آغاز و پس از آن بازی را شروع میکند (فقط میزبان).
|
// startTable شمارش معکوس را آغاز و پس از آن بازی را شروع میکند (فقط میزبان).
|
||||||
func (h *Hub) startTable(c *Client, hands int) {
|
func (h *Hub) startTable(c *Client, hands, stake int) {
|
||||||
t := h.tables[c.tableCode]
|
t := h.tables[c.tableCode]
|
||||||
if t == nil || t.host != c.UserID || t.started {
|
if t == nil || t.host != c.UserID || t.started {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
t.hands = clampHands(hands) // تعداد دستِ انتخابیِ میزبان (۳/۵/۷)
|
t.hands = clampHands(hands) // تعداد دستِ انتخابیِ میزبان (۳/۵/۷)
|
||||||
|
if stake >= 0 {
|
||||||
|
t.stake = stake // آخرین شرطِ سکهای (اگر همراهِ شروع فرستاده شد)
|
||||||
|
}
|
||||||
t.started = true
|
t.started = true
|
||||||
cd := mustJSON(countdownMsg{Type: "countdown", Seconds: 3})
|
cd := mustJSON(countdownMsg{Type: "countdown", Seconds: 3})
|
||||||
for _, cl := range t.clients {
|
for _, cl := range t.clients {
|
||||||
@@ -298,6 +304,7 @@ func (h *Hub) onStartTable(code string) {
|
|||||||
room.tier = defaultTier // برای جایزه/ورودی بیاثر است (میز خصوصی)
|
room.tier = defaultTier // برای جایزه/ورودی بیاثر است (میز خصوصی)
|
||||||
room.private = true
|
room.private = true
|
||||||
room.handsOverride = clampHands(t.hands) // تعداد دستِ انتخابیِ میزبان
|
room.handsOverride = clampHands(t.hands) // تعداد دستِ انتخابیِ میزبان
|
||||||
|
room.stake = int64(t.stake) // شرطِ سکهایِ هر بازیکن
|
||||||
for s := 0; s < 4; s++ {
|
for s := 0; s < 4; s++ {
|
||||||
if seats[s].isBot {
|
if seats[s].isBot {
|
||||||
continue
|
continue
|
||||||
@@ -312,6 +319,22 @@ func (h *Hub) onStartTable(code string) {
|
|||||||
slog.Info("private room started", "room", room.ID, "code", code, "humans", humans)
|
slog.Info("private room started", "room", room.ID, "code", code, "humans", humans)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// setStake شرطِ سکهایِ میز را تنظیم و برای همه پخش میکند (فقط میزبان، پیش از شروع).
|
||||||
|
func (h *Hub) setStake(c *Client, stake int) {
|
||||||
|
t := h.tables[c.tableCode]
|
||||||
|
if t == nil || t.host != c.UserID || t.started {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if stake < 0 {
|
||||||
|
stake = 0
|
||||||
|
}
|
||||||
|
if stake > 1_000_000 {
|
||||||
|
stake = 1_000_000
|
||||||
|
}
|
||||||
|
t.stake = stake
|
||||||
|
h.broadcastLobby(t)
|
||||||
|
}
|
||||||
|
|
||||||
// rotateTable جایگاهِ بازیکنانِ غیرِمیزبان را دورِ میز میچرخاند (فقط میزبان).
|
// rotateTable جایگاهِ بازیکنانِ غیرِمیزبان را دورِ میز میچرخاند (فقط میزبان).
|
||||||
// میزبان همیشه جایگاهِ اولش را حفظ میکند؛ چرخش تیمبندی را تغییر میدهد.
|
// میزبان همیشه جایگاهِ اولش را حفظ میکند؛ چرخش تیمبندی را تغییر میدهد.
|
||||||
func (h *Hub) rotateTable(c *Client, dir int) {
|
func (h *Hub) rotateTable(c *Client, dir int) {
|
||||||
@@ -379,7 +402,7 @@ func (h *Hub) broadcastLobby(t *pendingTable) {
|
|||||||
cl.trySend(mustJSON(tableLobbyMsg{
|
cl.trySend(mustJSON(tableLobbyMsg{
|
||||||
Type: "table_lobby", Code: t.code, Players: players,
|
Type: "table_lobby", Code: t.code, Players: players,
|
||||||
Host: cl.UserID == t.host, YouSeat: t.seatOf(i),
|
Host: cl.UserID == t.host, YouSeat: t.seatOf(i),
|
||||||
Remaining: remaining, Unlimited: unlimited,
|
Remaining: remaining, Unlimited: unlimited, Stake: t.stake,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ type inboundMsg struct {
|
|||||||
Dir int `json:"dir"` // برای rotate_table: جهتِ چرخشِ جایگاهها (+۱/−۱)
|
Dir int `json:"dir"` // برای rotate_table: جهتِ چرخشِ جایگاهها (+۱/−۱)
|
||||||
Text string `json:"text"` // برای chat: متنِ پیامِ آماده
|
Text string `json:"text"` // برای chat: متنِ پیامِ آماده
|
||||||
Emoji string `json:"emoji"` // برای chat: شناسه/کاراکترِ شکلک
|
Emoji string `json:"emoji"` // برای chat: شناسه/کاراکترِ شکلک
|
||||||
|
Stake int `json:"stake"` // برای set_stake/start_table: شرطِ سکهایِ میز
|
||||||
}
|
}
|
||||||
|
|
||||||
// chatMsg پیامِ چت/شکلکِ یک بازیکن که برای همهی میز پخش میشود.
|
// chatMsg پیامِ چت/شکلکِ یک بازیکن که برای همهی میز پخش میشود.
|
||||||
@@ -40,6 +41,7 @@ type tableLobbyMsg struct {
|
|||||||
YouSeat int `json:"you_seat"` // جایگاهِ مطلقِ گیرنده (برای چیدمانِ صحنه)
|
YouSeat int `json:"you_seat"` // جایگاهِ مطلقِ گیرنده (برای چیدمانِ صحنه)
|
||||||
Remaining int `json:"remaining"` // باقیماندهی میزهای رایگانِ گیرنده
|
Remaining int `json:"remaining"` // باقیماندهی میزهای رایگانِ گیرنده
|
||||||
Unlimited bool `json:"unlimited"`
|
Unlimited bool `json:"unlimited"`
|
||||||
|
Stake int `json:"stake"` // شرطِ سکهایِ فعلیِ میز (تنظیمِ میزبان)
|
||||||
}
|
}
|
||||||
|
|
||||||
// countdownMsg شمارش معکوس پیش از شروع بازیِ خصوصی.
|
// countdownMsg شمارش معکوس پیش از شروع بازیِ خصوصی.
|
||||||
|
|||||||
+49
-4
@@ -49,8 +49,11 @@ type Room struct {
|
|||||||
ID string
|
ID string
|
||||||
hub *Hub
|
hub *Hub
|
||||||
tier string
|
tier string
|
||||||
private bool // میز دورهمی: بدون ورودی/جایزه (فقط آمار ثبت میشود)
|
private bool // میز دورهمی: بدون ورودی/جایزهی tier (فقط آمار و شرطِ سکه)
|
||||||
handsOverride int // تعداد دستِ سفارشیِ میز خصوصی (۰ یعنی از tier)
|
handsOverride int // تعداد دستِ سفارشیِ میز خصوصی (۰ یعنی از tier)
|
||||||
|
stake int64 // شرطِ سکهایِ هر بازیکن در میز خصوصی (۰ یعنی رایگان)
|
||||||
|
pot int64 // مجموعِ سکههای جمعآوریشده برای برنده
|
||||||
|
paid [4]bool // جایگاههایی که شرط را پرداختهاند (برای بازگشت هنگام لغو)
|
||||||
settler Settler
|
settler Settler
|
||||||
seats [4]*seatInfo
|
seats [4]*seatInfo
|
||||||
game *game.Game
|
game *game.Game
|
||||||
@@ -122,6 +125,19 @@ func (r *Room) start() {
|
|||||||
}
|
}
|
||||||
r.game = game.NewGame(target, game.FirstHakem())
|
r.game = game.NewGame(target, game.FirstHakem())
|
||||||
r.graceUntil = time.Now().Add(r.firstHandDelay) // بُر زدن + پخشِ ۵ کارت
|
r.graceUntil = time.Now().Add(r.firstHandDelay) // بُر زدن + پخشِ ۵ کارت
|
||||||
|
// شرطِ سکهایِ میزِ خصوصی: از هر بازیکنِ انسان کسر و به pot اضافه میشود
|
||||||
|
// (هر که موجودی نداشت، رایگان بازی میکند و به pot اضافه نمیشود).
|
||||||
|
if r.stake > 0 {
|
||||||
|
for seat, s := range r.seats {
|
||||||
|
if s.isBot || s.userID == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := r.settler.ChargeCoins(s.userID, r.stake, r.ID); err == nil {
|
||||||
|
r.pot += r.stake
|
||||||
|
r.paid[seat] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// اطلاعاتِ نمایشیِ بازیکنان (سکه و نشانِ رتبه) را یکبار هنگام شروع میگیریم.
|
// اطلاعاتِ نمایشیِ بازیکنان (سکه و نشانِ رتبه) را یکبار هنگام شروع میگیریم.
|
||||||
for _, s := range r.seats {
|
for _, s := range r.seats {
|
||||||
if s.isBot || s.userID == 0 {
|
if s.isBot || s.userID == 0 {
|
||||||
@@ -462,26 +478,55 @@ func (r *Room) endRoom() {
|
|||||||
|
|
||||||
// settleGameOver جایزه را به برندگانِ انسان میدهد و بازی را ثبت میکند.
|
// settleGameOver جایزه را به برندگانِ انسان میدهد و بازی را ثبت میکند.
|
||||||
func (r *Room) settleGameOver(res *game.HandResult) {
|
func (r *Room) settleGameOver(res *game.HandResult) {
|
||||||
|
// برندگانِ انسان (برای تقسیمِ potِ میزِ خصوصی).
|
||||||
|
var winners []int64
|
||||||
for seat, s := range r.seats {
|
for seat, s := range r.seats {
|
||||||
if s.isBot || s.userID == 0 {
|
if s.isBot || s.userID == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
won := game.Team(seat) == res.WinnerTeam
|
won := game.Team(seat) == res.WinnerTeam
|
||||||
if won && !r.private {
|
if won {
|
||||||
r.settler.AwardWinner(s.userID, r.tier) // میز دورهمی جایزهی سکه ندارد
|
winners = append(winners, s.userID)
|
||||||
|
if !r.private {
|
||||||
|
r.settler.AwardWinner(s.userID, r.tier)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if !r.private {
|
if !r.private {
|
||||||
r.settler.AwardRank(s.userID, r.tier, won) // امتیازِ رتبه (اگر میزِ رتبهبندی باشد)
|
r.settler.AwardRank(s.userID, r.tier, won) // امتیازِ رتبه (اگر میزِ رتبهبندی باشد)
|
||||||
}
|
}
|
||||||
r.settler.RecordGameResult(s.userID, won)
|
r.settler.RecordGameResult(s.userID, won)
|
||||||
}
|
}
|
||||||
|
// تقسیمِ potِ شرطِ خصوصی بینِ برندگانِ انسان (باقیمانده به نفرِ اول).
|
||||||
|
if r.private && r.pot > 0 && len(winners) > 0 {
|
||||||
|
share := r.pot / int64(len(winners))
|
||||||
|
rem := r.pot - share*int64(len(winners))
|
||||||
|
for i, id := range winners {
|
||||||
|
amt := share
|
||||||
|
if i == 0 {
|
||||||
|
amt += rem
|
||||||
|
}
|
||||||
|
r.settler.AwardCoins(id, amt, r.ID)
|
||||||
|
}
|
||||||
|
r.pot = 0
|
||||||
|
}
|
||||||
r.settler.RecordGame(r.ID, r.playersJSON(res.WinnerTeam), res.WinnerTeam, res.Kot)
|
r.settler.RecordGame(r.ID, r.playersJSON(res.WinnerTeam), res.WinnerTeam, res.Kot)
|
||||||
}
|
}
|
||||||
|
|
||||||
// refundAll ورودی را به بازیکنانِ انسان که داوطلبانه خارج نشدهاند بازمیگرداند.
|
// refundAll ورودی را به بازیکنانِ انسان که داوطلبانه خارج نشدهاند بازمیگرداند.
|
||||||
func (r *Room) refundAll() {
|
func (r *Room) refundAll() {
|
||||||
if r.private {
|
if r.private {
|
||||||
return // میز دورهمی ورودی نگرفته، پس بازگشتی هم ندارد
|
// میزِ خصوصی: فقط شرطِ سکهایِ پرداختشده بازمیگردد (اگر شرطی بوده).
|
||||||
|
if r.stake > 0 {
|
||||||
|
for seat, s := range r.seats {
|
||||||
|
if s.isBot || s.left || s.userID == 0 || !r.paid[seat] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
r.settler.AwardCoins(s.userID, r.stake, r.ID+":refund")
|
||||||
|
r.paid[seat] = false
|
||||||
|
}
|
||||||
|
r.pot = 0
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
for _, s := range r.seats {
|
for _, s := range r.seats {
|
||||||
if s.isBot || s.left || s.userID == 0 {
|
if s.isBot || s.left || s.userID == 0 {
|
||||||
|
|||||||
@@ -16,6 +16,10 @@ type Settler interface {
|
|||||||
PlayerProfile(userID int64) (coins int64, rankTier string)
|
PlayerProfile(userID int64) (coins int64, rankTier string)
|
||||||
// ChargeTicket یک بلیت برای بُرِ مجدد در فازِ انتخابِ حکم مصرف میکند.
|
// ChargeTicket یک بلیت برای بُرِ مجدد در فازِ انتخابِ حکم مصرف میکند.
|
||||||
ChargeTicket(userID int64) error
|
ChargeTicket(userID int64) error
|
||||||
|
// ChargeCoins/AwardCoins برای شرطِ سکهایِ میزِ خصوصی (pot). Charge در صورتِ
|
||||||
|
// کمبودِ موجودی خطا میدهد؛ Award سکه را به برنده اضافه میکند.
|
||||||
|
ChargeCoins(userID int64, amount int64, ref string) error
|
||||||
|
AwardCoins(userID int64, amount int64, ref string)
|
||||||
// میز خصوصی (دورهمی).
|
// میز خصوصی (دورهمی).
|
||||||
ChargePrivateTable(userID int64) error // مصرفِ یک میز رایگان (خطا اگر سقف پر باشد)
|
ChargePrivateTable(userID int64) error // مصرفِ یک میز رایگان (خطا اگر سقف پر باشد)
|
||||||
PrivateTableInfo(userID int64) (remaining int, unlimited bool)
|
PrivateTableInfo(userID int64) (remaining int, unlimited bool)
|
||||||
@@ -36,6 +40,8 @@ func (noopSettler) RecordCut(int64) {}
|
|||||||
func (noopSettler) RecordGameResult(int64, bool) {}
|
func (noopSettler) RecordGameResult(int64, bool) {}
|
||||||
func (noopSettler) PlayerProfile(int64) (int64, string) { return 0, "" }
|
func (noopSettler) PlayerProfile(int64) (int64, string) { return 0, "" }
|
||||||
func (noopSettler) ChargeTicket(int64) error { return nil }
|
func (noopSettler) ChargeTicket(int64) error { return nil }
|
||||||
|
func (noopSettler) ChargeCoins(int64, int64, string) error { return nil }
|
||||||
|
func (noopSettler) AwardCoins(int64, int64, string) {}
|
||||||
func (noopSettler) ChargePrivateTable(int64) error { return nil }
|
func (noopSettler) ChargePrivateTable(int64) error { return nil }
|
||||||
func (noopSettler) PrivateTableInfo(int64) (int, bool) { return 0, true }
|
func (noopSettler) PrivateTableInfo(int64) (int, bool) { return 0, true }
|
||||||
func (noopSettler) TargetHands(string) int { return 7 }
|
func (noopSettler) TargetHands(string) int { return 7 }
|
||||||
|
|||||||
@@ -483,6 +483,8 @@ func (s *recordingSettler) RecordGameResult(int64, bool) {
|
|||||||
|
|
||||||
func (s *recordingSettler) PlayerProfile(int64) (int64, string) { return 0, "" }
|
func (s *recordingSettler) PlayerProfile(int64) (int64, string) { return 0, "" }
|
||||||
func (s *recordingSettler) ChargeTicket(int64) error { return nil }
|
func (s *recordingSettler) ChargeTicket(int64) error { return nil }
|
||||||
|
func (s *recordingSettler) ChargeCoins(int64, int64, string) error { return nil }
|
||||||
|
func (s *recordingSettler) AwardCoins(int64, int64, string) {}
|
||||||
func (s *recordingSettler) ChargePrivateTable(int64) error { return nil }
|
func (s *recordingSettler) ChargePrivateTable(int64) error { return nil }
|
||||||
func (s *recordingSettler) PrivateTableInfo(int64) (int, bool) { return 5, false }
|
func (s *recordingSettler) PrivateTableInfo(int64) (int, bool) { return 5, false }
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user