Files
tiennm99 4c068849c4 refactor(server): rename database package to lobby, drop ratel legacy files
Rename
- server/database/ → server/lobby/ (17 import sites + qualified references)
- Package was named "database" but there is no database — it's in-memory
  hashmaps for players/rooms/spectators. "lobby" accurately names its role:
  the multiplayer game lobby state (who is in what room, joining/leaving,
  spectating).

Ratel-online legacy cleanup
- Delete server/README.md — 100% Chinese, entirely about ratel card games
  (斗地主/跑得快/德州扑克/麻将/骗子酒馆/Uno). Zero relevance to gomoku.
- Delete server/build.sh + server/build.ps1 — multi-platform ratel-server
  build scripts with mixed Chinese comments, superseded by Makefile +
  Dockerfile.
- Delete server/docs/ — Chinese Docker deployment guide + quickstart,
  superseded by root docs/deployment-guide.md and root README.md.
- Delete server/demo.gif — ratel card-game demo screenshot.

Comment fixes: update "database" references in consts/const.go and
game/board.go package docs to point at "lobby" instead.

go vet + go test ./... green.
2026-04-11 16:08:46 +07:00

71 lines
2.0 KiB
Go

package network
import (
"time"
"github.com/gorilla/websocket"
"github.com/tiennm99/gomoku/server/lobby"
"github.com/tiennm99/gomoku/server/pkg/log"
)
const (
// maxMessageSize caps inbound WS frame size at 4 KB.
// Protobuf game messages are tiny; anything larger is malicious or buggy.
maxMessageSize = 4 * 1024
// readDeadline is the maximum time to wait for a frame before considering
// the client dead. Clients send heartbeats every ~50 s; 90 s gives headroom.
readDeadline = 90 * time.Second
)
// readLoop blocks until the connection closes or an unrecoverable read error occurs.
// It decodes each binary WS frame into a Request and dispatches it.
// On exit it closes the player's CmdCh and removes them from the store.
func readLoop(conn *websocket.Conn, player *lobby.Player) {
conn.SetReadLimit(maxMessageSize)
_ = conn.SetReadDeadline(time.Now().Add(readDeadline))
// Extend deadline on each ping/pong so the 90 s window is per-message, not absolute.
conn.SetPongHandler(func(string) error {
return conn.SetReadDeadline(time.Now().Add(readDeadline))
})
defer func() {
// Signal state machine and writer goroutine that this player is gone.
if player.CmdCh != nil {
close(player.CmdCh)
}
lobby.RemovePlayer(player.ID)
log.Infof("[reader] player %d disconnected\n", player.ID)
}()
for {
msgType, data, err := conn.ReadMessage()
if err != nil {
if websocket.IsUnexpectedCloseError(err,
websocket.CloseGoingAway,
websocket.CloseNormalClosure,
websocket.CloseNoStatusReceived) {
log.Errorf("[reader] player %d read error: %v\n", player.ID, err)
}
return
}
if msgType != websocket.BinaryMessage {
log.Errorf("[reader] player %d: unexpected message type %d, dropping\n", player.ID, msgType)
continue
}
// Reset deadline on any incoming frame (heartbeats count).
_ = conn.SetReadDeadline(time.Now().Add(readDeadline))
req, err := DecodeRequest(data)
if err != nil {
log.Errorf("[reader] player %d decode error: %v\n", player.ID, err)
continue
}
Dispatch(player, req)
}
}