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