feat: add private and profile

This commit is contained in:
2026-06-17 11:32:51 +03:30
parent 02060a4dc0
commit 0827858752
23 changed files with 972 additions and 25 deletions
+132
View File
@@ -7,6 +7,7 @@ import (
"strconv"
"strings"
"sync"
"sync/atomic"
"testing"
"time"
@@ -27,6 +28,8 @@ type anyMsg struct {
LeadSuit string `json:"lead_suit"`
TrickDone bool `json:"trick_done"`
Players []PlayerInfo `json:"players"`
Code string `json:"code"` // table_lobby
Host bool `json:"host"` // table_lobby
}
// TestFourPlayersFullGame یک بازی کامل را روی WebSocket واقعی با ۴ ربات اجرا می‌کند.
@@ -119,10 +122,117 @@ func testHub() *Hub {
hub.botDelay = 8 * time.Millisecond
hub.matchWait = 300 * time.Millisecond
hub.trickHold = 5 * time.Millisecond
hub.countdownDelay = 20 * time.Millisecond
go hub.Run()
return hub
}
// TestPrivateTableFlow: میزبان میز خصوصی می‌سازد، بازیکن دوم می‌پیوندد،
// میزبان شروع می‌کند و بازی (با بات برای ۲ جای خالی) تا پایان پیش می‌رود.
func TestPrivateTableFlow(t *testing.T) {
hub := testHub()
srv := httptest.NewServer(http.HandlerFunc(hub.ServeWS))
defer srv.Close()
wsURL := "ws" + strings.TrimPrefix(srv.URL, "http")
dial := func(tok string) *websocket.Conn {
c, _, err := websocket.DefaultDialer.Dial(wsURL+"?token="+tok, nil)
if err != nil {
t.Fatalf("dial %s: %v", tok, err)
}
return c
}
host := dial("1")
defer host.Close()
if err := host.WriteJSON(map[string]string{"type": "create_table"}); err != nil {
t.Fatal(err)
}
// میزبان باید table_lobby با کد دریافت کند.
code := ""
for code == "" {
_ = host.SetReadDeadline(time.Now().Add(2 * time.Second))
_, raw, err := host.ReadMessage()
if err != nil {
t.Fatalf("host read: %v", err)
}
var m anyMsg
_ = json.Unmarshal(raw, &m)
if m.Type == "table_lobby" {
if !m.Host {
t.Fatal("host flag must be true for creator")
}
code = m.Code
}
}
if len(code) != 5 {
t.Fatalf("expected 5-digit code, got %q", code)
}
joiner := dial("2")
defer joiner.Close()
if err := joiner.WriteJSON(map[string]string{"type": "join_table", "code": code}); err != nil {
t.Fatal(err)
}
// میزبان منتظر می‌ماند تا میز ۲ نفره شود، سپس شروع می‌کند.
var gotCountdown atomic.Bool
play := func(conn *websocket.Conn, m *anyMsg) {
if m.Type == "state" {
act(conn, m)
}
}
done := make(chan string, 2)
run := func(conn *websocket.Conn, isHost bool) {
started := false
for {
_ = conn.SetReadDeadline(time.Now().Add(6 * time.Second))
_, raw, err := conn.ReadMessage()
if err != nil {
done <- "read err: " + err.Error()
return
}
var m anyMsg
_ = json.Unmarshal(raw, &m)
switch m.Type {
case "table_lobby":
if isHost && !started && len(m.Players) >= 2 {
started = true
_ = conn.WriteJSON(map[string]string{"type": "start_table"})
}
case "countdown":
gotCountdown.Store(true)
case "state":
play(conn, &m)
case "game_over":
done <- "ok"
return
case "error":
done <- "error: " + string(raw)
return
}
}
}
go run(host, true)
go run(joiner, false)
for i := 0; i < 2; i++ {
select {
case res := <-done:
if res != "ok" {
t.Fatalf("client finished with: %s", res)
}
case <-time.After(25 * time.Second):
t.Fatal("private game did not finish in time")
}
}
if !gotCountdown.Load() {
t.Fatal("expected countdown message before game start")
}
}
// TestDisconnectMidGameNoPanic: قطع بازیکن وسط بازی باید بدون panic، رویداد
// player_disconnected تولید کند و بازی با حرکت خودکار ادامه یابد.
func TestDisconnectMidGameNoPanic(t *testing.T) {
@@ -323,6 +433,9 @@ type recordingSettler struct {
refunds int
awards int
records int
hands int
cuts int
results int
}
func (s *recordingSettler) ChargeEntry(int64, string) error {
@@ -347,6 +460,25 @@ func (s *recordingSettler) RecordGame(string, string, int, bool) {
s.mu.Unlock()
}
func (s *recordingSettler) RecordHand(int64, bool, bool, bool) {
s.mu.Lock()
s.hands++
s.mu.Unlock()
}
func (s *recordingSettler) RecordCut(int64) {
s.mu.Lock()
s.cuts++
s.mu.Unlock()
}
func (s *recordingSettler) RecordGameResult(int64, bool) {
s.mu.Lock()
s.results++
s.mu.Unlock()
}
func (s *recordingSettler) ChargePrivateTable(int64) error { return nil }
func (s *recordingSettler) PrivateTableInfo(int64) (int, bool) { return 5, false }
// برای سرعتِ تست، بازی پس از ۲ هَند تمام می‌شود.
func (s *recordingSettler) TargetHands(string) int { return 2 }
func (s *recordingSettler) snapshot() (c, r, a, rec int) {