This commit is contained in:
2026-07-05 18:41:12 +03:30
parent 37baef565e
commit 045e304853
8 changed files with 42 additions and 24 deletions
+4 -4
View File
@@ -73,13 +73,13 @@ func (g gameSettler) RecordGameResult(userID int64, won bool) {
g.eco.RecordGameResult(context.Background(), userID, won) g.eco.RecordGameResult(context.Background(), userID, won)
} }
// PlayerProfile سکه و نشانِ رتبه‌ی بازیکن را برای نمایش سرِ میز برمی‌گرداند. // PlayerProfile سکه، نشانِ رتبه و آواتارِ بازیکن را برای نمایش سرِ میز برمی‌گرداند.
func (g gameSettler) PlayerProfile(userID int64) (int64, string) { func (g gameSettler) PlayerProfile(userID int64) (int64, string, string) {
p, err := g.eco.GetProfile(context.Background(), userID) p, err := g.eco.GetProfile(context.Background(), userID)
if err != nil || p == nil { if err != nil || p == nil {
return 0, "" return 0, "", ""
} }
return p.Coins, p.RankTier return p.Coins, p.RankTier, p.Avatar
} }
// ChargeTicket یک بلیت برای بُرِ مجدد مصرف می‌کند (خطا اگر بلیت کافی نباشد). // ChargeTicket یک بلیت برای بُرِ مجدد مصرف می‌کند (خطا اگر بلیت کافی نباشد).
+16 -5
View File
@@ -11,18 +11,29 @@ cd "$(dirname "$0")"
echo "==> build linux/amd64 (CGO off, pure-Go sqlite)" echo "==> build linux/amd64 (CGO off, pure-Go sqlite)"
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o /tmp/hakemsho-linux ./cmd/server CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o /tmp/hakemsho-linux ./cmd/server
LOCAL_SHA=$(shasum -a 256 /tmp/hakemsho-linux | cut -d' ' -f1)
echo " sha256=$LOCAL_SHA"
echo "==> upload to $HOST" echo "==> upload to $HOST"
scp -i "$KEY" -o StrictHostKeyChecking=no /tmp/hakemsho-linux "$HOST:$REMOTE_DIR/hakemsho.new" scp -i "$KEY" -o StrictHostKeyChecking=no /tmp/hakemsho-linux "$HOST:$REMOTE_DIR/hakemsho.new"
echo "==> swap in + restart (zero-config; data & env untouched)" # چک‌سام را روی سرور تأیید کن و فقط در صورتِ سالم‌بودن باینری را جابه‌جا کن.
ssh -i "$KEY" -o StrictHostKeyChecking=no "$HOST" ' # این از سناریوی «انتقالِ ناقص ⇒ باینریِ خراب ⇒ SEGV» جلوگیری می‌کند.
echo "==> verify checksum + swap in + restart (data & env untouched)"
ssh -i "$KEY" -o StrictHostKeyChecking=no "$HOST" "
set -e set -e
REMOTE_SHA=\$(sha256sum ~/hakemsho/hakemsho.new | cut -d' ' -f1)
if [ \"\$REMOTE_SHA\" != \"$LOCAL_SHA\" ]; then
echo \"checksum mismatch (\$REMOTE_SHA != $LOCAL_SHA) — aborting, service untouched\"
rm -f ~/hakemsho/hakemsho.new
exit 1
fi
chmod +x ~/hakemsho/hakemsho.new
mv ~/hakemsho/hakemsho.new ~/hakemsho/hakemsho mv ~/hakemsho/hakemsho.new ~/hakemsho/hakemsho
chmod +x ~/hakemsho/hakemsho sudo systemctl reset-failed hakemsho || true
sudo systemctl restart hakemsho sudo systemctl restart hakemsho
sleep 2 sleep 3
systemctl is-active hakemsho systemctl is-active hakemsho
curl -s -m5 http://127.0.0.1:8080/health && echo curl -s -m5 http://127.0.0.1:8080/health && echo
' "
echo "==> done: https://hakem.approagency.ir/health" echo "==> done: https://hakem.approagency.ir/health"
+5 -3
View File
@@ -60,21 +60,23 @@ type Profile struct {
RankPoints int64 `json:"rank_points"` RankPoints int64 `json:"rank_points"`
RankTier string `json:"rank_tier"` // bronze..king RankTier string `json:"rank_tier"` // bronze..king
RankIndex int `json:"rank_index"` // ۰..۴ RankIndex int `json:"rank_index"` // ۰..۴
Avatar string `json:"avatar"` // seed آواتار (برای نمایش سرِ میز)
} }
// GetProfile وضعیت اقتصادی کاربر را می‌خواند. // GetProfile وضعیت اقتصادی کاربر را می‌خواند.
func (s *Service) GetProfile(ctx context.Context, userID int64) (*Profile, error) { func (s *Service) GetProfile(ctx context.Context, userID int64) (*Profile, error) {
var p Profile var p Profile
var vipUntil sql.NullString var vipUntil, avatar sql.NullString
err := s.db.QueryRowContext(ctx, err := s.db.QueryRowContext(ctx,
`SELECT coins, tickets, xp, trophies, vip_until, selected_card, selected_carpet, rank_points FROM users WHERE id = ?`, userID). `SELECT coins, tickets, xp, trophies, vip_until, selected_card, selected_carpet, rank_points, avatar FROM users WHERE id = ?`, userID).
Scan(&p.Coins, &p.Tickets, &p.XP, &p.Trophies, &vipUntil, &p.SelectedCard, &p.SelectedCarpet, &p.RankPoints) Scan(&p.Coins, &p.Tickets, &p.XP, &p.Trophies, &vipUntil, &p.SelectedCard, &p.SelectedCarpet, &p.RankPoints, &avatar)
if errors.Is(err, sql.ErrNoRows) { if errors.Is(err, sql.ErrNoRows) {
return nil, ErrNotFound return nil, ErrNotFound
} }
if err != nil { if err != nil {
return nil, err return nil, err
} }
p.Avatar = avatar.String
p.Level, p.XPInto, p.XPNeed = LevelInfo(p.XP) p.Level, p.XPInto, p.XPNeed = LevelInfo(p.XP)
p.RankTier, p.RankIndex = RankTier(p.RankPoints) p.RankTier, p.RankIndex = RankTier(p.RankPoints)
if vipUntil.Valid { if vipUntil.Valid {
+5 -3
View File
@@ -391,10 +391,12 @@ func (h *Hub) removeFromTable(t *pendingTable, c *Client) {
func (h *Hub) broadcastLobby(t *pendingTable) { func (h *Hub) broadcastLobby(t *pendingTable) {
players := make([]lobbyPlayer, len(t.clients)) players := make([]lobbyPlayer, len(t.clients))
for i, cl := range t.clients { for i, cl := range t.clients {
_, _, avatar := h.settler.PlayerProfile(cl.UserID)
players[i] = lobbyPlayer{ players[i] = lobbyPlayer{
Name: cl.Name, Name: cl.Name,
Host: cl.UserID == t.host, Avatar: avatar,
Seat: t.seatOf(i), Host: cl.UserID == t.host,
Seat: t.seatOf(i),
} }
} }
for i, cl := range t.clients { for i, cl := range t.clients {
+5 -3
View File
@@ -27,9 +27,10 @@ type chatMsg struct {
// lobbyPlayer یک بازیکن در اتاق انتظارِ میز خصوصی. // lobbyPlayer یک بازیکن در اتاق انتظارِ میز خصوصی.
type lobbyPlayer struct { type lobbyPlayer struct {
Name string `json:"name"` Name string `json:"name"`
Host bool `json:"host"` Avatar string `json:"avatar"` // seed آواتارِ کاربر
Seat int `json:"seat"` // جایگاهِ مطلق (۰..۳) Host bool `json:"host"`
Seat int `json:"seat"` // جایگاهِ مطلق (۰..۳)
} }
// tableLobbyMsg وضعیت اتاق انتظارِ میز خصوصی (دورهمی). // tableLobbyMsg وضعیت اتاق انتظارِ میز خصوصی (دورهمی).
@@ -64,6 +65,7 @@ type PlayerInfo struct {
Connected bool `json:"connected"` Connected bool `json:"connected"`
Coins int64 `json:"coins"` Coins int64 `json:"coins"`
RankTier string `json:"rank_tier"` // bronze..king (برای نشانِ رتبه) RankTier string `json:"rank_tier"` // bronze..king (برای نشانِ رتبه)
Avatar string `json:"avatar"` // seed آواتارِ کاربر
} }
// matchedMsg به کلاینت پس از تشکیل میز. // matchedMsg به کلاینت پس از تشکیل میز.
+3 -2
View File
@@ -39,6 +39,7 @@ type seatInfo struct {
left bool // خروج دائمی داوطلبانه (در تسویه از بازگشت ورودی محروم) left bool // خروج دائمی داوطلبانه (در تسویه از بازگشت ورودی محروم)
coins int64 // برای نمایش سرِ میز coins int64 // برای نمایش سرِ میز
rankTier string // نشانِ رتبه برای نمایش سرِ میز rankTier string // نشانِ رتبه برای نمایش سرِ میز
avatar string // seed آواتارِ انتخابیِ کاربر (برای نمایش سرِ میز)
} }
// auto مشخص می‌کند جایگاه باید خودکار بازی شود (بات یا قطع‌شده). // auto مشخص می‌کند جایگاه باید خودکار بازی شود (بات یا قطع‌شده).
@@ -143,7 +144,7 @@ func (r *Room) start() {
if s.isBot || s.userID == 0 { if s.isBot || s.userID == 0 {
continue continue
} }
s.coins, s.rankTier = r.settler.PlayerProfile(s.userID) s.coins, s.rankTier, s.avatar = r.settler.PlayerProfile(s.userID)
} }
for seat, s := range r.seats { for seat, s := range r.seats {
if s.client != nil { if s.client != nil {
@@ -567,7 +568,7 @@ func (r *Room) playersInfo() []PlayerInfo {
out[i] = PlayerInfo{ out[i] = PlayerInfo{
Seat: i, Name: s.name, Bot: s.isBot, Seat: i, Name: s.name, Bot: s.isBot,
Connected: s.isBot || s.connected, Connected: s.isBot || s.connected,
Coins: s.coins, RankTier: s.rankTier, Coins: s.coins, RankTier: s.rankTier, Avatar: s.avatar,
} }
} }
return out return out
+3 -3
View File
@@ -12,8 +12,8 @@ type Settler interface {
RecordHand(userID int64, won, kot, asHakem bool) // پایان یک هَند RecordHand(userID int64, won, kot, asHakem bool) // پایان یک هَند
RecordCut(userID int64) // بُرِش با حکم RecordCut(userID int64) // بُرِش با حکم
RecordGameResult(userID int64, won bool) // پایان بازی (برد/باخت) RecordGameResult(userID int64, won bool) // پایان بازی (برد/باخت)
// PlayerProfile سکه و نشانِ رتبه‌ی بازیکن را برای نمایش سرِ میز برمی‌گرداند. // PlayerProfile سکه، نشانِ رتبه و seedِ آواتارِ بازیکن را برای نمایش سرِ میز برمی‌گرداند.
PlayerProfile(userID int64) (coins int64, rankTier string) PlayerProfile(userID int64) (coins int64, rankTier string, avatar string)
// ChargeTicket یک بلیت برای بُرِ مجدد در فازِ انتخابِ حکم مصرف می‌کند. // ChargeTicket یک بلیت برای بُرِ مجدد در فازِ انتخابِ حکم مصرف می‌کند.
ChargeTicket(userID int64) error ChargeTicket(userID int64) error
// ChargeCoins/AwardCoins برای شرطِ سکه‌ایِ میزِ خصوصی (pot). Charge در صورتِ // ChargeCoins/AwardCoins برای شرطِ سکه‌ایِ میزِ خصوصی (pot). Charge در صورتِ
@@ -38,7 +38,7 @@ func (noopSettler) RecordGame(string, string, int, bool) {}
func (noopSettler) RecordHand(int64, bool, bool, bool) {} func (noopSettler) RecordHand(int64, bool, bool, bool) {}
func (noopSettler) RecordCut(int64) {} 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, 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) ChargeCoins(int64, int64, string) error { return nil }
func (noopSettler) AwardCoins(int64, int64, string) {} func (noopSettler) AwardCoins(int64, int64, string) {}
+1 -1
View File
@@ -481,7 +481,7 @@ func (s *recordingSettler) RecordGameResult(int64, bool) {
s.mu.Unlock() s.mu.Unlock()
} }
func (s *recordingSettler) PlayerProfile(int64) (int64, string) { return 0, "" } func (s *recordingSettler) PlayerProfile(int64) (int64, string, 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) ChargeCoins(int64, int64, string) error { return nil }
func (s *recordingSettler) AwardCoins(int64, int64, string) {} func (s *recordingSettler) AwardCoins(int64, int64, string) {}