feat: add documentation, code comments, and update Docker config

Rewrite README with usage guide, deployment instructions, and protocol
docs. Update CLAUDE.md to reflect gomoku-only architecture. Add English
doc comments to all key server Go files, replacing Chinese comments.

Create docs/system-architecture.md (state machine, protocol, database
schema) and docs/deployment-guide.md (local dev, Docker, production
nginx, resource requirements).

Update Dockerfile to Go 1.22 with repo-root build context to include
web client. Update docker-compose to match.
This commit is contained in:
2026-04-09 23:35:46 +07:00
parent cdcc3e0623
commit 5ccd4e7ce2
15 changed files with 652 additions and 164 deletions
+14 -16
View File
@@ -16,13 +16,14 @@ import (
"github.com/ratel-online/server/consts"
)
// In-memory data store. All state is volatile — server restart clears everything.
var roomIds int64 = 0
var players = hashmap.New() // 存储连接过服务器的全部用户
var connPlayers = hashmap.New()
var rooms = hashmap.New()
var roomPlayers = hashmap.New()
var roomSpectators = hashmap.New()
var roomKickedPlayers = hashmap.New()
var players = hashmap.New() // all players ever connected (by ID)
var connPlayers = hashmap.New() // currently connected players
var rooms = hashmap.New() // active rooms (by room ID)
var roomPlayers = hashmap.New() // map[roomID] -> map[playerID]bool
var roomSpectators = hashmap.New() // map[roomID] -> map[playerID]int (join order)
var roomKickedPlayers = hashmap.New() // map[roomID] -> map[playerID]bool
var roomPropsSetter = map[string]func(r *Room, v string){
consts.RoomPropsPassword: func(r *Room, v string) {
if v == "off" {
@@ -59,9 +60,9 @@ func Connected(conn *network.Conn, info *modelx.AuthInfo) *Player {
Name: strings.Desensitize(info.Name),
Amount: 2000,
}
player.Conn(conn) // 初始化play对象
players.Set(conn.ID(), player) // 写入用户池
connPlayers.Set(conn.ID(), player) // 写入连接用户池
player.Conn(conn)
players.Set(conn.ID(), player)
connPlayers.Set(conn.ID(), player)
return player
}
@@ -122,12 +123,11 @@ func getPlayer(playerId int64) *Player {
}
func SetRoomProps(room *Room, k, v string) {
// 根据房间类型限制可设置的属性
// Only allow properties appropriate for the game type
allowedProps := getAllowedPropsByGameType(room.Type)
// 检查属性是否允许设置
if !allowedProps[k] {
return // 不允许的属性直接返回,不执行设置
return
}
if setter, ok := roomPropsSetter[k]; ok {
@@ -181,9 +181,9 @@ func IsValidPlayer(roomId, playerId int64) bool {
return false
}
// 加入房间
// JoinRoom adds a player to a room. If the room is full or running, the player
// becomes a spectator instead. Returns an error if the player was previously kicked.
func JoinRoom(roomId, playerId int64) error {
// 资源检查
player := getPlayer(playerId)
if player == nil {
return consts.ErrorsExist
@@ -193,7 +193,6 @@ func JoinRoom(roomId, playerId int64) error {
return consts.ErrorsRoomInvalid
}
// 加锁防止并发异常
room.Lock()
defer room.Unlock()
@@ -203,7 +202,6 @@ func JoinRoom(roomId, playerId int64) error {
room.ActiveTime = time.Now()
//房间人数及状态检查
if room.Players >= room.MaxPlayers || room.State == consts.RoomStateRunning {
spectatorsIds := getRoomSpectators(roomId)
spectatorsIds[playerId] = len(spectatorsIds)
+26 -6
View File
@@ -14,6 +14,7 @@ import (
"github.com/ratel-online/server/consts"
)
// Role represents a player's role within a room.
type Role string
const (
@@ -22,6 +23,12 @@ const (
RoleSpectator Role = "spectator"
)
// Player represents a connected user. Public fields are serializable state;
// private fields manage the network connection and state machine lifecycle.
//
// The `data` channel receives packets from the client when `read` is true
// (between StartTransaction/StopTransaction). The state machine goroutine
// reads from this channel via AskFor* methods.
type Player struct {
ID int64 `json:"id"`
IP string `json:"ip"`
@@ -32,11 +39,11 @@ type Player struct {
RoomID int64 `json:"roomId"`
Role Role `json:"role"`
conn *network.Conn
data chan *protocol.Packet
read bool
state consts.StateID
online bool
conn *network.Conn // underlying network connection
data chan *protocol.Packet // buffered channel for client input
read bool // true when accepting input (inside a transaction)
state consts.StateID // current state machine state
online bool // false after disconnect
}
func (p *Player) Write(bytes []byte) error {
@@ -49,6 +56,8 @@ func (p *Player) IsOnline() bool {
return p.online
}
// Offline marks the player as disconnected, closes the connection, and
// cleans up room membership. Called when the network read loop exits.
func (p *Player) Offline() {
p.online = false
_ = p.conn.Close()
@@ -65,6 +74,9 @@ func (p *Player) Offline() {
}
}
// Listening is the main read loop. It reads packets from the network connection
// and forwards them to the data channel when the state machine is accepting input.
// Blocks until the connection is closed or errors.
func (p *Player) Listening() error {
loopCount := 0
for {
@@ -83,7 +95,8 @@ func (p *Player) Listening() error {
}
}
// 向客户端发生消息
// WriteString sends a text message to the client. The 30ms sleep prevents
// message flooding when the server sends multiple messages in rapid succession.
func (p *Player) WriteString(data string) error {
time.Sleep(30 * time.Millisecond)
return p.conn.Write(protocol.Packet{
@@ -165,11 +178,14 @@ func (p *Player) AskForStringWithoutTransaction(timeout ...time.Duration) (strin
return packet.String(), nil
}
// StartTransaction enables input acceptance and sends the INTERACTIVE_SIGNAL_START
// marker to the client. The web client uses this to know when to enable the input field.
func (p *Player) StartTransaction() {
p.read = true
_ = p.WriteString(consts.IsStart)
}
// StopTransaction disables input acceptance and sends INTERACTIVE_SIGNAL_STOP.
func (p *Player) StopTransaction() {
p.read = false
_ = p.WriteString(consts.IsStop)
@@ -200,10 +216,14 @@ func (p Player) String() string {
return fmt.Sprintf("%s[%d]", p.Name, p.ID)
}
// RoomGame is implemented by each game type's state struct (e.g., Gomoku).
// Clean() is called when the room is deleted to close channels and free resources.
type RoomGame interface {
Clean()
}
// Room represents a game room. Players join a room, wait for enough players,
// then the owner starts the game. The Game field holds the active game state.
type Room struct {
sync.Mutex