feat: add private and profile
This commit is contained in:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user