mirror of
https://github.com/tiennm99/gomoku.git
synced 2026-05-14 04:58:38 +00:00
4c068849c4
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.
98 lines
2.9 KiB
Go
98 lines
2.9 KiB
Go
package network
|
|
|
|
import (
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/gorilla/websocket"
|
|
|
|
"github.com/tiennm99/gomoku/server/lobby"
|
|
"github.com/tiennm99/gomoku/server/pkg/log"
|
|
"github.com/tiennm99/gomoku/server/protocol"
|
|
"github.com/tiennm99/gomoku/server/state"
|
|
)
|
|
|
|
const (
|
|
// sendChSize is the number of outbound responses that can be buffered
|
|
// per player before back-pressure kicks in and Send() returns an error.
|
|
sendChSize = 32
|
|
|
|
// cmdChSize is the number of stateful requests queued for the state machine.
|
|
// Phase-06 rewrites the state machine; for now it remains the legacy loop.
|
|
cmdChSize = 16
|
|
)
|
|
|
|
// upgrader accepts any origin for development. Phase-11 hardens this with an allowlist.
|
|
var upgrader = websocket.Upgrader{
|
|
ReadBufferSize: 1024,
|
|
WriteBufferSize: 1024,
|
|
CheckOrigin: func(r *http.Request) bool {
|
|
return true
|
|
},
|
|
}
|
|
|
|
// Server is the WebSocket-only game server.
|
|
// It binds a single HTTP endpoint: /gomoku
|
|
type Server struct {
|
|
addr string
|
|
}
|
|
|
|
// NewServer creates a Server that will listen on addr (e.g. ":1999").
|
|
func NewServer(addr string) *Server {
|
|
return &Server{addr: addr}
|
|
}
|
|
|
|
// Serve registers the /gomoku handler and blocks on ListenAndServe.
|
|
func (s *Server) Serve() error {
|
|
mux := http.NewServeMux()
|
|
mux.HandleFunc("/gomoku", s.handleWS)
|
|
log.Infof("[server] WebSocket server listening on %s/gomoku\n", s.addr)
|
|
return http.ListenAndServe(s.addr, mux)
|
|
}
|
|
|
|
// handleWS upgrades the HTTP connection to WebSocket, registers the player,
|
|
// wires I/O channels, then spawns reader + writer goroutines.
|
|
func (s *Server) handleWS(w http.ResponseWriter, r *http.Request) {
|
|
conn, err := upgrader.Upgrade(w, r, nil)
|
|
if err != nil {
|
|
log.Errorf("[server] WS upgrade failed: %v\n", err)
|
|
return
|
|
}
|
|
|
|
// Register player with an empty nickname (set later via SetNicknameRequest).
|
|
player := lobby.RegisterPlayer("")
|
|
player.SendCh = make(chan *protocol.Response, sendChSize)
|
|
player.CmdCh = make(chan *protocol.Request, cmdChSize)
|
|
player.LastHeartbeat = time.Now()
|
|
|
|
log.Infof("[server] player %d connected from %s\n", player.ID, r.RemoteAddr)
|
|
|
|
// Send ClientConnectResponse so the client knows its assigned ID.
|
|
_ = player.Send(&protocol.Response{
|
|
Payload: &protocol.Response_ClientConnect{
|
|
ClientConnect: &protocol.ClientConnectResponse{
|
|
ClientId: int32(player.ID),
|
|
},
|
|
},
|
|
})
|
|
|
|
// Send initial nickname prompt (invalid_length = 0 signals "prompt mode").
|
|
_ = player.Send(&protocol.Response{
|
|
Payload: &protocol.Response_NicknameSet{
|
|
NicknameSet: &protocol.NicknameSetResponse{InvalidLength: 0},
|
|
},
|
|
})
|
|
|
|
wr := newWriter(conn, player.SendCh)
|
|
|
|
// Writer goroutine: serialises all WS writes.
|
|
go wr.run()
|
|
|
|
// State machine goroutine: reads typed *protocol.Request from player.CmdCh.
|
|
// Stateful requests are routed here by Dispatch; stateless ones handled inline.
|
|
go state.Run(player)
|
|
|
|
// Reader loop: blocks until WS close or error, then cleans up.
|
|
readLoop(conn, player)
|
|
}
|