feat: add time for heart beat

This commit is contained in:
2026-06-19 02:29:00 +03:30
parent 0827858752
commit 32f0406e63
4 changed files with 38 additions and 23 deletions
+5 -2
View File
@@ -12,10 +12,11 @@ import (
// مقادیر پیش‌فرض مقاوم‌سازی (قابل بازنویسی در تست).
const (
defaultTurnTimeout = 20 * time.Second // مهلت نوبت بازیکنِ انسان
defaultTurnTimeout = 30 * time.Second // مهلت نوبت بازیکنِ انسان
defaultBotDelay = 800 * time.Millisecond // تأخیر حرکت بات/قطع‌شده (حس طبیعی + فرصت بازگشت)
defaultMatchWait = 12 * time.Second // مهلت پر شدن میز با انسان پیش از افزودن بات
defaultMatchWait = 6 * time.Second // مهلت پر شدن میز با انسان پیش از افزودن بات
defaultTrickHold = 1200 * time.Millisecond // مدت نمایش دستِ کامل پیش از جمع‌آوری
defaultFirstHandDelay = 3000 * time.Millisecond // تأخیر اولین حرکتِ بات تا پایانِ اینترو+بُر زدنِ کلاینت
)
// AuthFunc توکن را تأیید کرده و (شناسه کاربر، نام نمایشی) را برمی‌گرداند.
@@ -69,6 +70,7 @@ type Hub struct {
botDelay time.Duration
matchWait time.Duration
trickHold time.Duration
firstHandDelay time.Duration // تأخیر اولین حرکتِ بات (هماهنگ با انیمیشنِ بُر زدنِ کلاینت)
countdownDelay time.Duration // مهلت شمارش معکوسِ میز خصوصی پیش از شروع
register chan *Client
@@ -98,6 +100,7 @@ func NewHub(auth AuthFunc) *Hub {
botDelay: defaultBotDelay,
matchWait: defaultMatchWait,
trickHold: defaultTrickHold,
firstHandDelay: defaultFirstHandDelay,
countdownDelay: 3 * time.Second,
register: make(chan *Client),
unregister: make(chan *Client),
+2 -1
View File
@@ -79,6 +79,7 @@ type stateMsg struct {
TricksWon [2]int `json:"tricks_won"`
Scores [2]int `json:"scores"`
TargetScore int `json:"target_score"`
TurnTimeoutMs int `json:"turn_timeout_ms"` // مهلتِ نوبتِ انسان (برای شمارنده‌ی کلاینت)
Players []PlayerInfo `json:"players"`
}
@@ -118,7 +119,7 @@ type playerStatusMsg struct {
}
// buildState نمای بازی را برای جایگاه seat می‌سازد (کارت بقیه مخفی می‌ماند).
func buildState(g *game.Game, roomID string, seat int, players []PlayerInfo) stateMsg {
func buildState(g *game.Game, roomID string, seat int, players []PlayerInfo, turnTimeoutMs int) stateMsg {
hand := g.Hand(seat)
handStr := make([]string, len(hand))
for i, c := range hand {
+12 -2
View File
@@ -58,6 +58,8 @@ type Room struct {
turnTimeout time.Duration
botDelay time.Duration
trickHold time.Duration
firstHandDelay time.Duration
handStart time.Time // زمانِ شروعِ هَندِ جاری (برای پنجره‌ی بُر زدن)
}
func newRoom(id string, seats [4]*seatInfo, hub *Hub) *Room {
@@ -70,6 +72,7 @@ func newRoom(id string, seats [4]*seatInfo, hub *Hub) *Room {
turnTimeout: hub.turnTimeout,
botDelay: hub.botDelay,
trickHold: hub.trickHold,
firstHandDelay: hub.firstHandDelay,
}
}
@@ -108,6 +111,7 @@ func (r *Room) start() {
target = targetScore
}
r.game = game.NewGame(target, game.FirstHakem())
r.handStart = time.Now()
for seat, s := range r.seats {
if s.client != nil {
s.client.trySend(mustJSON(r.matchedFor(seat)))
@@ -142,6 +146,7 @@ func (r *Room) react() {
r.endRoom()
return
}
r.handStart = time.Now() // شروعِ هَندِ جدید ⇒ پنجره‌ی بُر زدن
r.broadcastState()
r.armTimer()
case game.PhaseGameOver:
@@ -206,6 +211,11 @@ func (r *Room) armTimer() {
delay := r.turnTimeout
if r.seats[actor].auto() {
delay = r.botDelay
// در پنجره‌ی ابتدای هَند (انیمیشنِ بُر زدنِ کلاینت)، حرکتِ خودکار را عقب
// می‌اندازیم تا کارت‌ها وسطِ بُر زدن روی میز نیفتند. نوبتِ انسان دست‌نخورده می‌ماند.
if grace := r.firstHandDelay - time.Since(r.handStart); grace > delay {
delay = grace
}
}
r.timer = time.AfterFunc(delay, func() {
select {
@@ -386,7 +396,7 @@ func (r *Room) handleReconnect(seat int, client *Client) {
r.broadcast(mustJSON(playerStatusMsg{Type: "player_reconnected", Seat: seat}))
// وضعیت کامل را برای بازیکن بازگشته بفرست
client.trySend(mustJSON(r.matchedFor(seat)))
client.trySend(mustJSON(buildState(r.game, r.ID, seat, r.playersInfo())))
client.trySend(mustJSON(buildState(r.game, r.ID, seat, r.playersInfo(), int(r.turnTimeout.Milliseconds()))))
r.armTimer() // مهلت نوبت برای انسانِ بازگشته دوباره بلند می‌شود
}
@@ -476,7 +486,7 @@ func (r *Room) broadcastState() {
players := r.playersInfo()
for seat, s := range r.seats {
if s.client != nil {
s.client.trySend(mustJSON(buildState(r.game, r.ID, seat, players)))
s.client.trySend(mustJSON(buildState(r.game, r.ID, seat, players, int(r.turnTimeout.Milliseconds()))))
}
}
}
+1
View File
@@ -122,6 +122,7 @@ func testHub() *Hub {
hub.botDelay = 8 * time.Millisecond
hub.matchWait = 300 * time.Millisecond
hub.trickHold = 5 * time.Millisecond
hub.firstHandDelay = 0
hub.countdownDelay = 20 * time.Millisecond
go hub.Run()
return hub