feat: add rotate private table
This commit is contained in:
+83
-21
@@ -48,9 +48,18 @@ type pendingTable struct {
|
||||
code string
|
||||
host int64
|
||||
clients []*Client
|
||||
hands int // تعداد دستِ انتخابشده توسط میزبان (۳/۵/۷)
|
||||
rot int // چرخشِ جایگاههای غیرِمیزبان (۰..۲)
|
||||
started bool // پس از فشردن «شروع» توسط میزبان
|
||||
}
|
||||
|
||||
// nonHostSeat جایگاهِ مطلقِ j-اُمین بازیکنِ غیرِمیزبان را با چرخشِ rot برمیگرداند.
|
||||
// میزبان همیشه جایگاه ۰ است؛ سه جایگاهِ دیگر {۱،۲،۳} با چرخش جابهجا میشوند.
|
||||
func nonHostSeat(j, rot int) int {
|
||||
seats := [3]int{1, 2, 3}
|
||||
return seats[((j+rot)%3+3)%3]
|
||||
}
|
||||
|
||||
// Hub هماهنگکننده مرکزی: اتصالها، صف matchmaking و مسیریابی پیامها.
|
||||
// تمام state آن فقط توسط goroutine Run تغییر میکند (single-writer، بدون قفل).
|
||||
// پس از ساخت یک میز، هاب دیگر به seatInfo دست نمیزند (مالک آن goroutine میز است).
|
||||
@@ -179,7 +188,9 @@ func (h *Hub) handle(in inbound) {
|
||||
case "join_table":
|
||||
h.joinTable(c, in.msg.Code)
|
||||
case "start_table":
|
||||
h.startTable(c)
|
||||
h.startTable(c, in.msg.Hands)
|
||||
case "rotate_table":
|
||||
h.rotateTable(c, in.msg.Dir)
|
||||
case "leave_table":
|
||||
h.leaveTable(c)
|
||||
}
|
||||
@@ -234,11 +245,12 @@ func (h *Hub) joinTable(c *Client, code string) {
|
||||
}
|
||||
|
||||
// startTable شمارش معکوس را آغاز و پس از آن بازی را شروع میکند (فقط میزبان).
|
||||
func (h *Hub) startTable(c *Client) {
|
||||
func (h *Hub) startTable(c *Client, hands int) {
|
||||
t := h.tables[c.tableCode]
|
||||
if t == nil || t.host != c.UserID || t.started {
|
||||
return
|
||||
}
|
||||
t.hands = clampHands(hands) // تعداد دستِ انتخابیِ میزبان (۳/۵/۷)
|
||||
t.started = true
|
||||
cd := mustJSON(countdownMsg{Type: "countdown", Seconds: 3})
|
||||
for _, cl := range t.clients {
|
||||
@@ -262,35 +274,60 @@ func (h *Hub) onStartTable(code string) {
|
||||
delete(h.tables, code)
|
||||
|
||||
var seats [4]*seatInfo
|
||||
n := 0
|
||||
for _, cl := range t.clients {
|
||||
if n >= 4 || !h.clients[cl] {
|
||||
continue // قطعشدهها نادیده گرفته میشوند
|
||||
humans := 0
|
||||
for i, cl := range t.clients {
|
||||
if i >= 4 || !h.clients[cl] {
|
||||
continue // قطعشدهها نادیده گرفته میشوند (جایشان را بات میگیرد)
|
||||
}
|
||||
seats[n] = &seatInfo{client: cl, userID: cl.UserID, name: cl.Name, connected: true}
|
||||
n++
|
||||
seat := t.seatOf(i)
|
||||
seats[seat] = &seatInfo{client: cl, userID: cl.UserID, name: cl.Name, connected: true}
|
||||
humans++
|
||||
}
|
||||
if n == 0 {
|
||||
if humans == 0 {
|
||||
return // همه خارج شدند
|
||||
}
|
||||
for i := n; i < 4; i++ {
|
||||
h.botSeq++
|
||||
seats[i] = &seatInfo{isBot: true, name: fmt.Sprintf("ربات %d", h.botSeq)}
|
||||
for s := 0; s < 4; s++ {
|
||||
if seats[s] == nil {
|
||||
h.botSeq++
|
||||
seats[s] = &seatInfo{isBot: true, name: fmt.Sprintf("ربات %d", h.botSeq)}
|
||||
}
|
||||
}
|
||||
|
||||
h.roomSeq++
|
||||
room := newRoom(fmt.Sprintf("p%d", h.roomSeq), seats, h)
|
||||
room.tier = defaultTier // قواعدِ مبتدی برای دورهمی
|
||||
room.tier = defaultTier // برای جایزه/ورودی بیاثر است (میز خصوصی)
|
||||
room.private = true
|
||||
for i := 0; i < n; i++ {
|
||||
cl := seats[i].client
|
||||
room.handsOverride = clampHands(t.hands) // تعداد دستِ انتخابیِ میزبان
|
||||
for s := 0; s < 4; s++ {
|
||||
if seats[s].isBot {
|
||||
continue
|
||||
}
|
||||
cl := seats[s].client
|
||||
cl.room = room
|
||||
cl.seat = i
|
||||
cl.seat = s
|
||||
cl.tableCode = ""
|
||||
h.locations[cl.UserID] = location{room: room, seat: i}
|
||||
h.locations[cl.UserID] = location{room: room, seat: s}
|
||||
}
|
||||
go room.run()
|
||||
slog.Info("private room started", "room", room.ID, "code", code, "humans", n)
|
||||
slog.Info("private room started", "room", room.ID, "code", code, "humans", humans)
|
||||
}
|
||||
|
||||
// rotateTable جایگاهِ بازیکنانِ غیرِمیزبان را دورِ میز میچرخاند (فقط میزبان).
|
||||
// میزبان همیشه جایگاهِ اولش را حفظ میکند؛ چرخش تیمبندی را تغییر میدهد.
|
||||
func (h *Hub) rotateTable(c *Client, dir int) {
|
||||
t := h.tables[c.tableCode]
|
||||
if t == nil || t.host != c.UserID || t.started {
|
||||
return
|
||||
}
|
||||
if len(t.clients) < 2 {
|
||||
return // جز میزبان کسی نیست ⇒ چیزی برای چرخش نمانده
|
||||
}
|
||||
d := 1
|
||||
if dir < 0 {
|
||||
d = -1
|
||||
}
|
||||
t.rot = ((t.rot+d)%3 + 3) % 3
|
||||
h.broadcastLobby(t)
|
||||
}
|
||||
|
||||
// leaveTable خروجِ داوطلبانه از اتاق انتظار.
|
||||
@@ -331,17 +368,42 @@ func (h *Hub) removeFromTable(t *pendingTable, c *Client) {
|
||||
func (h *Hub) broadcastLobby(t *pendingTable) {
|
||||
players := make([]lobbyPlayer, len(t.clients))
|
||||
for i, cl := range t.clients {
|
||||
players[i] = lobbyPlayer{Name: cl.Name, Host: cl.UserID == t.host}
|
||||
players[i] = lobbyPlayer{
|
||||
Name: cl.Name,
|
||||
Host: cl.UserID == t.host,
|
||||
Seat: t.seatOf(i),
|
||||
}
|
||||
}
|
||||
for _, cl := range t.clients {
|
||||
for i, cl := range t.clients {
|
||||
remaining, unlimited := h.settler.PrivateTableInfo(cl.UserID)
|
||||
cl.trySend(mustJSON(tableLobbyMsg{
|
||||
Type: "table_lobby", Code: t.code, Players: players,
|
||||
Host: cl.UserID == t.host, Remaining: remaining, Unlimited: unlimited,
|
||||
Host: cl.UserID == t.host, YouSeat: t.seatOf(i),
|
||||
Remaining: remaining, Unlimited: unlimited,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
// seatOf جایگاهِ مطلقِ کلاینتِ شماره i در فهرستِ میز را برمیگرداند (میزبان=۰).
|
||||
func (t *pendingTable) seatOf(i int) int {
|
||||
if i == 0 {
|
||||
return 0
|
||||
}
|
||||
return nonHostSeat(i-1, t.rot)
|
||||
}
|
||||
|
||||
// clampHands تعداد دست را به یکی از مقادیرِ مجاز (۳/۵/۷) محدود میکند.
|
||||
func clampHands(h int) int {
|
||||
switch h {
|
||||
case 5:
|
||||
return 5
|
||||
case 7:
|
||||
return 7
|
||||
default:
|
||||
return 3
|
||||
}
|
||||
}
|
||||
|
||||
// enqueue کلاینت را پس از کسر ورودی به صفِ tier افزوده و میزهای کامل را میسازد.
|
||||
func (h *Hub) enqueue(c *Client, tier string) {
|
||||
if c.room != nil {
|
||||
|
||||
@@ -10,12 +10,15 @@ type inboundMsg struct {
|
||||
Suit string `json:"suit"` // برای choose_trump: hearts|spades|diamonds|clubs
|
||||
Card string `json:"card"` // برای play_card: مثل "AS"
|
||||
Code string `json:"code"` // برای join_table: شماره میز خصوصی
|
||||
Hands int `json:"hands"` // برای start_table: تعداد دستِ بازیِ خصوصی (۳/۵/۷)
|
||||
Dir int `json:"dir"` // برای rotate_table: جهتِ چرخشِ جایگاهها (+۱/−۱)
|
||||
}
|
||||
|
||||
// lobbyPlayer یک بازیکن در اتاق انتظارِ میز خصوصی.
|
||||
type lobbyPlayer struct {
|
||||
Name string `json:"name"`
|
||||
Host bool `json:"host"`
|
||||
Seat int `json:"seat"` // جایگاهِ مطلق (۰..۳)
|
||||
}
|
||||
|
||||
// tableLobbyMsg وضعیت اتاق انتظارِ میز خصوصی (دورهمی).
|
||||
@@ -24,6 +27,7 @@ type tableLobbyMsg struct {
|
||||
Code string `json:"code"`
|
||||
Players []lobbyPlayer `json:"players"`
|
||||
Host bool `json:"host"` // آیا گیرنده میزبان است
|
||||
YouSeat int `json:"you_seat"` // جایگاهِ مطلقِ گیرنده (برای چیدمانِ صحنه)
|
||||
Remaining int `json:"remaining"` // باقیماندهی میزهای رایگانِ گیرنده
|
||||
Unlimited bool `json:"unlimited"`
|
||||
}
|
||||
|
||||
+6
-2
@@ -46,8 +46,9 @@ func (s *seatInfo) auto() bool { return s.isBot || !s.connected }
|
||||
type Room struct {
|
||||
ID string
|
||||
hub *Hub
|
||||
tier string
|
||||
private bool // میز دورهمی: بدون ورودی/جایزه (فقط آمار ثبت میشود)
|
||||
tier string
|
||||
private bool // میز دورهمی: بدون ورودی/جایزه (فقط آمار ثبت میشود)
|
||||
handsOverride int // تعداد دستِ سفارشیِ میز خصوصی (۰ یعنی از tier)
|
||||
settler Settler
|
||||
seats [4]*seatInfo
|
||||
game *game.Game
|
||||
@@ -110,6 +111,9 @@ func (r *Room) run() {
|
||||
func (r *Room) start() {
|
||||
// تعداد هَندِ لازم برای برد بازی از روی نوعِ میز (tier) تعیین میشود.
|
||||
target := r.settler.TargetHands(r.tier)
|
||||
if r.handsOverride > 0 {
|
||||
target = r.handsOverride // میز خصوصی: تعداد دستِ انتخابیِ میزبان
|
||||
}
|
||||
if target <= 0 {
|
||||
target = targetScore
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user