refactor: rename Go module and copy core pkgs into server/pkg

- Module: github.com/ratel-online/server → github.com/tiennm99/gomoku/server
- Copied core/{log,util/async,util/json,util/strings,model,network,protocol,consts}
  into server/pkg/* (temporary shims until phase-05 replaces protocol/network)
- Rewrote all ratel-online import paths across server/**/*.go
- Trimmed main.go: single -p flag, WS-only on :1999, no TCP/bot/static
- Trimmed network/wss.go: endpoint /gomoku, no static file serving
- Updated Makefile: removed TCP/bot references, port 1999
This commit is contained in:
2026-04-11 12:04:03 +07:00
parent 5ccd4e7ce2
commit 1b9eec5f7d
27 changed files with 576 additions and 212 deletions
+15 -51
View File
@@ -1,45 +1,31 @@
.PHONY: help build run stop clean logs shell test prod-build prod-up prod-down
.PHONY: help build run stop logs shell clean test
# 检测 Docker Compose 命令
# Detect Docker Compose command
DOCKER_COMPOSE := $(shell which docker-compose 2>/dev/null)
ifeq ($(DOCKER_COMPOSE),)
DOCKER_COMPOSE := $(shell docker compose version >/dev/null 2>&1 && echo "docker compose" || echo "")
endif
ifeq ($(DOCKER_COMPOSE),)
$(error "Docker Compose 未安装。请安装 docker-compose 或升级到支持 'docker compose' 的 Docker 版本")
$(error "Docker Compose not installed. Install docker-compose or upgrade Docker.")
endif
# 默认目标
help:
@echo "Ratel游戏服务器 Docker 管理命令"
@echo "Gomoku server Docker management"
@echo ""
@echo "当前使用的 Docker Compose 命令: $(DOCKER_COMPOSE)"
@echo ""
@echo "开发环境命令:"
@echo " make build - 构建Docker镜像"
@echo " make run - 启动开发环境"
@echo " make stop - 停止开发环境"
@echo " make logs - 查看日志"
@echo " make shell - 进入容器shell"
@echo " make clean - 清理容器和镜像"
@echo ""
@echo "生产环境命令:"
@echo " make prod-build - 构建生产镜像"
@echo " make prod-up - 启动生产环境"
@echo " make prod-down - 停止生产环境"
@echo ""
@echo "测试命令:"
@echo " make test - 运行测试"
@echo " make build - Build Docker image"
@echo " make run - Start server (dev)"
@echo " make stop - Stop server"
@echo " make logs - View logs"
@echo " make shell - Open container shell"
@echo " make clean - Remove containers and images"
@echo " make test - Run Go tests"
# 开发环境命令
build:
$(DOCKER_COMPOSE) build
run:
$(DOCKER_COMPOSE) up -d
@echo "服务已启动:"
@echo " WebSocket: http://localhost:9998"
@echo " TCP: localhost:9999"
@echo "Server started: ws://localhost:1999/gomoku"
stop:
$(DOCKER_COMPOSE) down
@@ -48,33 +34,11 @@ logs:
$(DOCKER_COMPOSE) logs -f
shell:
$(DOCKER_COMPOSE) exec ratel-server sh
$(DOCKER_COMPOSE) exec gomoku-server sh
clean:
$(DOCKER_COMPOSE) down -v
docker rmi ratel-server:latest || true
docker rmi gomoku-server:latest || true
# 生产环境命令
prod-build:
docker build -t ratel-server:latest .
prod-up:
$(DOCKER_COMPOSE) -f docker-compose.prod.yaml up -d
@echo "生产环境已启动:"
@echo " WebSocket: http://localhost:9998"
@echo " TCP: localhost:9999"
@echo " HTTP: http://localhost:80"
prod-down:
$(DOCKER_COMPOSE) -f docker-compose.prod.yaml down
# 测试命令
test:
@echo "测试WebSocket连接..."
@curl -s -o /dev/null -w "HTTP状态码: %{http_code}\n" http://localhost:9998 || echo "WebSocket端口未响应"
@echo ""
@echo "测试TCP连接..."
@nc -zv localhost 9999 2>&1 || echo "TCP端口未响应"
@echo ""
@echo "测试Nginx健康检查..."
@curl -s http://localhost/health || echo "Nginx未运行"
go test ./...
+1 -1
View File
@@ -3,7 +3,7 @@ package consts
import (
"time"
"github.com/ratel-online/core/consts"
"github.com/tiennm99/gomoku/server/pkg/consts"
)
type StateID int
+7 -7
View File
@@ -7,13 +7,13 @@ import (
"time"
"github.com/awesome-cap/hashmap"
"github.com/ratel-online/core/log"
modelx "github.com/ratel-online/core/model"
"github.com/ratel-online/core/network"
"github.com/ratel-online/core/util/async"
"github.com/ratel-online/core/util/json"
"github.com/ratel-online/core/util/strings"
"github.com/ratel-online/server/consts"
"github.com/tiennm99/gomoku/server/pkg/log"
modelx "github.com/tiennm99/gomoku/server/pkg/model"
"github.com/tiennm99/gomoku/server/pkg/network"
"github.com/tiennm99/gomoku/server/pkg/async"
"github.com/tiennm99/gomoku/server/pkg/json"
"github.com/tiennm99/gomoku/server/pkg/strings"
"github.com/tiennm99/gomoku/server/consts"
)
// In-memory data store. All state is volatile — server restart clears everything.
+1 -1
View File
@@ -3,7 +3,7 @@ package database
import (
"sync"
"github.com/ratel-online/server/consts"
"github.com/tiennm99/gomoku/server/consts"
)
// Gomoku stores the state of a gomoku (five-in-a-row) game.
+6 -6
View File
@@ -6,12 +6,12 @@ import (
"sync"
"time"
"github.com/ratel-online/core/log"
"github.com/ratel-online/core/model"
"github.com/ratel-online/core/network"
"github.com/ratel-online/core/protocol"
"github.com/ratel-online/core/util/json"
"github.com/ratel-online/server/consts"
"github.com/tiennm99/gomoku/server/pkg/log"
"github.com/tiennm99/gomoku/server/pkg/model"
"github.com/tiennm99/gomoku/server/pkg/network"
"github.com/tiennm99/gomoku/server/pkg/protocol"
"github.com/tiennm99/gomoku/server/pkg/json"
"github.com/tiennm99/gomoku/server/consts"
)
// Role represents a player's role within a room.
+3 -6
View File
@@ -1,17 +1,14 @@
module github.com/ratel-online/server
module github.com/tiennm99/gomoku/server
go 1.22
require (
github.com/Szzrain/Milky-go-sdk v1.0.1
github.com/awesome-cap/hashmap v0.0.0-20211211100532-e3300ac4ae14
github.com/awesome-cap/hashmap v0.0.0-20220308123617-f10e2b637d7d
github.com/gorilla/websocket v1.5.3
github.com/ratel-online/core v0.0.0-20250225062905-81b6faff6d25
github.com/json-iterator/go v1.1.12
)
require (
github.com/awesome-cap/im v0.0.0-20210720090440-7556eb92965d // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/stretchr/testify v1.8.0 // indirect
+2 -39
View File
@@ -1,62 +1,25 @@
github.com/Szzrain/Milky-go-sdk v1.0.1 h1:dsKIUtW7lI7RGLOuNut2ChxqQpM+bqd+3AafCIIhYRc=
github.com/Szzrain/Milky-go-sdk v1.0.1/go.mod h1:vyl5G/6TQha+Ygf9ZWkDN//7dJFq2V0ezdk3Rm2kDKQ=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/awesome-cap/hashmap v0.0.0-20211211100532-e3300ac4ae14 h1:YH6IXdLClQbuKrhYdyXYzdE9cicwU7HaD7YDMmo0oRY=
github.com/awesome-cap/hashmap v0.0.0-20211211100532-e3300ac4ae14/go.mod h1:5vIRKw3P2HirHOPm1xQfJ6GmPfZQemzbIXfU0MqLlZU=
github.com/awesome-cap/im v0.0.0-20210720090440-7556eb92965d h1:lc5kZK/lqmqAfNeXcRPkwXKdwO/9fQ09Wpz9p/qp4So=
github.com/awesome-cap/im v0.0.0-20210720090440-7556eb92965d/go.mod h1:dIipyrtcBTc6up0MNw5w7ONoNKGksz2EGCVlcwiRjE4=
github.com/awesome-cap/hashmap v0.0.0-20220308123617-f10e2b637d7d h1:3UBn4jCuvUVEHoz/HONIhhOy8ry2hfemf+PDlKUQnMs=
github.com/awesome-cap/hashmap v0.0.0-20220308123617-f10e2b637d7d/go.mod h1:5vIRKw3P2HirHOPm1xQfJ6GmPfZQemzbIXfU0MqLlZU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/memberlist v0.2.4/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/ratel-online/core v0.0.0-20250225062905-81b6faff6d25 h1:9vdciSkTnXNoZkTqOe9rSP40PGRvNk/OzYxPCmWGh1c=
github.com/ratel-online/core v0.0.0-20250225062905-81b6faff6d25/go.mod h1:8SYaPGDk9dVGnUIEkanZvV1ErTqd8OdedKNCwyXgRjI=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+8 -42
View File
@@ -2,54 +2,20 @@ package main
import (
"flag"
"fmt"
"strconv"
"github.com/ratel-online/core/log"
"github.com/ratel-online/core/util/async"
"github.com/ratel-online/server/bot"
"github.com/ratel-online/server/network"
"github.com/tiennm99/gomoku/server/network"
"github.com/tiennm99/gomoku/server/pkg/log"
)
var (
Wsport int
Tcpport int
StaticDir string
BotAddr string
BotToken string
BotGroup int64
)
var port int
func main() {
flag.IntVar(&Wsport, "w", 9998, "WebsocketServer Port")
flag.IntVar(&Tcpport, "t", 9999, "TcpServer Port")
flag.StringVar(&StaticDir, "s", "../web", "Static files directory")
flag.StringVar(&BotAddr, "bot", "", "Bot connection address")
flag.StringVar(&BotToken, "bot-token", "", "Bot token")
flag.Int64Var(&BotGroup, "bot-group", 0, "Bot group ID")
flag.IntVar(&port, "p", 1999, "WebSocket server port")
flag.Parse()
// Connect QQ bot if configured
if BotAddr != "" && BotToken != "" && BotGroup != 0 {
err := bot.Connect(BotAddr, BotToken, BotGroup)
if err != nil {
log.Panic(fmt.Sprintf("连接Bot失败: %v", err))
}
// Send test message to the bot group
err = bot.SendGroupMessage(BotGroup, "Server started!")
if err != nil {
log.Errorf("发送群消息失败: %v", err)
} else {
log.Infof("已发送群消息到 %d", BotGroup)
}
defer bot.Close()
}
async.Async(func() {
wsServer := network.NewWebsocketServer(":" + strconv.Itoa(Wsport), StaticDir)
log.Panic(wsServer.Serve())
})
server := network.NewTcpServer(":" + strconv.Itoa(Tcpport))
log.Panic(server.Serve())
addr := ":" + strconv.Itoa(port)
log.Infof("Starting gomoku server on %s/gomoku\n", addr)
wsServer := network.NewWebsocketServer(addr)
log.Panic(wsServer.Serve())
}
+8 -8
View File
@@ -1,14 +1,14 @@
package network
import (
"github.com/ratel-online/core/log"
"github.com/ratel-online/core/model"
"github.com/ratel-online/core/network"
"github.com/ratel-online/core/protocol"
"github.com/ratel-online/core/util/async"
"github.com/ratel-online/server/consts"
"github.com/ratel-online/server/database"
"github.com/ratel-online/server/state"
"github.com/tiennm99/gomoku/server/pkg/log"
"github.com/tiennm99/gomoku/server/pkg/model"
"github.com/tiennm99/gomoku/server/pkg/network"
"github.com/tiennm99/gomoku/server/pkg/protocol"
"github.com/tiennm99/gomoku/server/pkg/async"
"github.com/tiennm99/gomoku/server/consts"
"github.com/tiennm99/gomoku/server/database"
"github.com/tiennm99/gomoku/server/state"
"time"
)
+27 -29
View File
@@ -1,47 +1,45 @@
package network
import (
"github.com/gorilla/websocket"
"github.com/ratel-online/core/log"
"github.com/ratel-online/core/protocol"
"net/http"
"net/http"
"github.com/gorilla/websocket"
"github.com/tiennm99/gomoku/server/pkg/log"
"github.com/tiennm99/gomoku/server/pkg/protocol"
)
// Websocket is a WebSocket-only HTTP server bound to a single /gomoku endpoint.
type Websocket struct {
addr string
StaticDir string
addr string
}
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool {
return true
},
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool {
return true
},
}
func NewWebsocketServer(addr string, staticDir string) Websocket {
return Websocket{addr: addr, StaticDir: staticDir}
// NewWebsocketServer creates a Websocket server listening on addr (e.g. ":1999").
func NewWebsocketServer(addr string) Websocket {
return Websocket{addr: addr}
}
func (w Websocket) Serve() error {
http.HandleFunc("/ws", serveWs)
if w.StaticDir != "" {
http.Handle("/", http.FileServer(http.Dir(w.StaticDir)))
log.Infof("Serving static files from %s\n", w.StaticDir)
}
log.Infof("Websocket server listener on %s\n", w.addr)
return http.ListenAndServe(w.addr, nil)
http.HandleFunc("/gomoku", serveWs)
log.Infof("WebSocket server listening on %s/gomoku\n", w.addr)
return http.ListenAndServe(w.addr, nil)
}
func serveWs(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Error(err)
return
}
err = handle(protocol.NewWebsocketReadWriteCloser(conn))
if err != nil{
log.Error(err)
}
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Error(err)
return
}
err = handle(protocol.NewWebsocketReadWriteCloser(conn))
if err != nil {
log.Error(err)
}
}
+31
View File
@@ -0,0 +1,31 @@
package async
import (
"bytes"
"fmt"
"runtime"
)
func Async(fun func()) {
go func() {
defer func() {
if err := recover(); err != nil {
PrintStackTrace(err)
}
}()
fun()
}()
}
func PrintStackTrace(err interface{}) {
buf := bytes.Buffer{}
buf.WriteString(fmt.Sprintf("%v\n", err))
for i := 1; ; i++ {
pc, file, line, ok := runtime.Caller(i)
if !ok {
break
}
buf.WriteString(fmt.Sprintf("%s:%d (0x%x)\n", file, line, pc))
}
fmt.Println(buf.String())
}
+7
View File
@@ -0,0 +1,7 @@
package consts
const (
IsStart = "INTERACTIVE_SIGNAL_START"
IsStop = "INTERACTIVE_SIGNAL_STOP"
MaxPacketSize = 65536
)
+26
View File
@@ -0,0 +1,26 @@
package json
import (
jsoniter "github.com/json-iterator/go"
"unsafe"
)
func init() {
jsoniter.RegisterTypeEncoderFunc("[]uint8", func(ptr unsafe.Pointer, stream *jsoniter.Stream) {
t := *((*[]byte)(ptr))
stream.WriteString(string(t))
}, nil)
jsoniter.RegisterTypeDecoderFunc("[]uint8", func(ptr unsafe.Pointer, iter *jsoniter.Iterator) {
str := iter.ReadString()
*((*[]byte)(ptr)) = []byte(str)
})
}
func Marshal(v interface{}) []byte {
data, _ := jsoniter.Marshal(v)
return data
}
func Unmarshal(data []byte, v interface{}) error {
return jsoniter.Unmarshal(data, v)
}
+42
View File
@@ -0,0 +1,42 @@
package log
import (
"fmt"
"path/filepath"
"runtime"
"time"
)
func sprintf(t string, format string, args ...interface{}) string {
_, path, line, _ := runtime.Caller(3)
_, file := filepath.Split(path)
return fmt.Sprintf(fmt.Sprintf("%s [%s] %s:%d %s", time.Now().Format("2006-01-02 15:04:05.999"), t, file, line, format), args...)
}
func printf(t string, format string, args ...interface{}) {
fmt.Printf(sprintf(t, format, args...))
}
func Infof(format string, args ...interface{}) {
printf("INFO", format, args...)
}
func Info(arg interface{}) {
printf("INFO", "%v\n", arg)
}
func Errorf(format string, args ...interface{}) {
printf("ERROR", format, args...)
}
func Error(arg interface{}) {
printf("ERROR", "%v\n", arg)
}
func Panicf(format string, args ...interface{}) {
panic(sprintf("PANIC", format, args...))
}
func Panic(arg interface{}) {
printf("PANIC", "%v\n", arg)
}
+59
View File
@@ -0,0 +1,59 @@
package model
// AuthInfo is sent by the client on first connection to identify itself.
type AuthInfo struct {
ID int64 `json:"id"`
Name string `json:"name"`
Score int64 `json:"score"`
}
type Data struct {
Code int `json:"code"`
Msg string `json:"msg"`
}
type Option struct {
ID int `json:"id"`
Name string `json:"name"`
}
type Player struct {
ID int64 `json:"id"`
Name string `json:"name"`
Score int64 `json:"score"`
Group int `json:"group"`
Pokers int `json:"pokers"`
}
type Room struct {
ID int64 `json:"id"`
Name string `json:"name"`
Type int `json:"type"`
TypeDesc string `json:"typeDesc"`
Players int `json:"players"`
State int `json:"state"`
StateDesc string `json:"stateDesc"`
Creator int64 `json:"creator"`
}
type Options struct {
Data
Options []Option `json:"options"`
}
type RoomList struct {
Data
Rooms []Room `json:"rooms"`
}
type RoomInfo struct {
Data
Room Room `json:"room"`
Players []Player `json:"players"`
}
type RoomEvent struct {
Data
Room Room `json:"room"`
Player Player `json:"player"`
}
+56
View File
@@ -0,0 +1,56 @@
package network
import (
"github.com/tiennm99/gomoku/server/pkg/protocol"
"sync/atomic"
)
var connId int64
type Conn struct {
id int64
state int
conn protocol.ReadWriteCloser
}
func Wrapper(conn protocol.ReadWriteCloser) *Conn {
return &Conn{
id: atomic.AddInt64(&connId, 1),
conn: conn,
}
}
func (c *Conn) ID() int64 {
return c.id
}
func (c *Conn) IP() string {
return c.conn.IP()
}
func (c *Conn) Close() error {
c.state = 1
return c.conn.Close()
}
func (c *Conn) State() int {
return c.state
}
func (c *Conn) Accept(apply func(msg protocol.Packet, c *Conn)) error {
for {
packet, err := c.conn.Read()
if err != nil {
return err
}
apply(*packet, c)
}
}
func (c *Conn) Write(packet protocol.Packet) error {
return c.conn.Write(packet)
}
func (c *Conn) Read() (*protocol.Packet, error) {
return c.conn.Read()
}
+96
View File
@@ -0,0 +1,96 @@
package protocol
import (
"encoding/binary"
"errors"
"github.com/tiennm99/gomoku/server/pkg/consts"
"github.com/tiennm99/gomoku/server/pkg/json"
"io"
"strconv"
)
var (
lenSize = 4
)
type Packet struct {
Body []byte `json:"data"`
}
func (p Packet) Int() (int, error) {
v, err := strconv.ParseInt(p.String(), 10, 64)
return int(v), err
}
func (p Packet) Int64() (int64, error) {
v, _ := strconv.ParseInt(p.String(), 10, 64)
return v, nil
}
func (p Packet) String() string {
return string(p.Body)
}
func (p Packet) Unmarshal(v interface{}) error {
return json.Unmarshal(p.Body, v)
}
func StringPacket(msg string) Packet {
return Packet{
Body: []byte(msg),
}
}
func ErrorPacket(err error) Packet {
return Packet{
Body: []byte(err.Error()),
}
}
func ObjectPacket(obj interface{}) Packet {
return Packet{
Body: json.Marshal(obj),
}
}
type ReadWriteCloser interface {
Read() (*Packet, error)
Write(msg Packet) error
Close() error
IP() string
}
func readUint32(reader io.Reader) (uint32, error) {
data := make([]byte, 4)
_, err := io.ReadFull(reader, data)
if err != nil {
return 0, err
}
return binary.BigEndian.Uint32(data), nil
}
func encode(msg Packet) []byte {
lenBytes := make([]byte, lenSize)
binary.BigEndian.PutUint32(lenBytes, uint32(len(msg.Body)))
data := make([]byte, 0)
data = append(data, lenBytes...)
return append(data, msg.Body...)
}
func decode(r io.Reader) (*Packet, error) {
l, err := readUint32(r)
if err != nil {
return nil, err
}
if l > consts.MaxPacketSize {
return nil, errors.New("Overflow max packet size " + strconv.Itoa(consts.MaxPacketSize))
}
dataBytes := make([]byte, l)
_, err = io.ReadFull(r, dataBytes)
if err != nil {
return nil, err
}
return &Packet{
Body: dataBytes,
}, nil
}
+30
View File
@@ -0,0 +1,30 @@
package protocol
import (
"net"
)
type TcpReadWriteCloser struct {
conn net.Conn
}
func NewTcpReadWriteCloser(conn net.Conn) TcpReadWriteCloser {
return TcpReadWriteCloser{conn: conn}
}
func (t TcpReadWriteCloser) Read() (*Packet, error) {
return decode(t.conn)
}
func (t TcpReadWriteCloser) Write(msg Packet) error {
_, err := t.conn.Write(encode(msg))
return err
}
func (t TcpReadWriteCloser) Close() error {
return t.conn.Close()
}
func (t TcpReadWriteCloser) IP() string {
return t.conn.RemoteAddr().String()
}
+36
View File
@@ -0,0 +1,36 @@
package protocol
import (
"github.com/gorilla/websocket"
"github.com/tiennm99/gomoku/server/pkg/json"
)
type WebsocketReadWriteCloser struct {
conn *websocket.Conn
}
func NewWebsocketReadWriteCloser(conn *websocket.Conn) WebsocketReadWriteCloser {
return WebsocketReadWriteCloser{conn: conn}
}
func (w WebsocketReadWriteCloser) Read() (*Packet, error) {
_, b, err := w.conn.ReadMessage()
if err != nil {
return nil, err
}
msg := &Packet{}
json.Unmarshal(b, msg)
return msg, nil
}
func (w WebsocketReadWriteCloser) Write(msg Packet) error {
return w.conn.WriteMessage(websocket.BinaryMessage, json.Marshal(msg))
}
func (w WebsocketReadWriteCloser) Close() error {
return w.conn.Close()
}
func (w WebsocketReadWriteCloser) IP() string {
return w.conn.RemoteAddr().String()
}
+93
View File
@@ -0,0 +1,93 @@
package strings
import (
"encoding/json"
"strconv"
"strings"
)
func String(dest interface{}) string {
var key string
if dest == nil {
return key
}
switch dest.(type) {
case float64:
key = strconv.FormatFloat(dest.(float64), 'f', -1, 64)
case *float64:
key = strconv.FormatFloat(*dest.(*float64), 'f', -1, 64)
case float32:
key = strconv.FormatFloat(float64(dest.(float32)), 'f', -1, 32)
case *float32:
key = strconv.FormatFloat(float64(*dest.(*float32)), 'f', -1, 32)
case int:
key = strconv.Itoa(dest.(int))
case *int:
key = strconv.Itoa(*dest.(*int))
case uint:
key = strconv.Itoa(int(dest.(uint)))
case *uint:
key = strconv.Itoa(int(*dest.(*uint)))
case int8:
key = strconv.Itoa(int(dest.(int8)))
case *int8:
key = strconv.Itoa(int(*dest.(*int8)))
case uint8:
key = strconv.Itoa(int(dest.(uint8)))
case *uint8:
key = strconv.Itoa(int(*dest.(*uint8)))
case int16:
key = strconv.Itoa(int(dest.(int16)))
case *int16:
key = strconv.Itoa(int(*dest.(*int16)))
case uint16:
key = strconv.Itoa(int(dest.(uint16)))
case *uint16:
key = strconv.Itoa(int(*dest.(*uint16)))
case int32:
key = strconv.Itoa(int(dest.(int32)))
case *int32:
key = strconv.Itoa(int(*dest.(*int32)))
case uint32:
key = strconv.Itoa(int(dest.(uint32)))
case *uint32:
key = strconv.Itoa(int(*dest.(*uint32)))
case int64:
key = strconv.FormatInt(dest.(int64), 10)
case *int64:
key = strconv.FormatInt(*dest.(*int64), 10)
case uint64:
key = strconv.FormatUint(dest.(uint64), 10)
case *uint64:
key = strconv.FormatUint(*dest.(*uint64), 10)
case string:
key = dest.(string)
case *string:
key = *dest.(*string)
case []byte:
key = string(dest.([]byte))
case *[]byte:
key = string(*dest.(*[]byte))
case bool:
if dest.(bool) {
key = "true"
} else {
key = "false"
}
case *bool:
if *dest.(*bool) {
key = "true"
} else {
key = "false"
}
default:
newValue, _ := json.Marshal(dest)
key = string(newValue)
}
return key
}
func Desensitize(str string) string {
// Placeholder — original had Chinese word substitutions not relevant to gomoku.
return strings.TrimSpace(str)
}
+2 -2
View File
@@ -5,8 +5,8 @@ import (
"errors"
"fmt"
"github.com/ratel-online/server/consts"
"github.com/ratel-online/server/database"
"github.com/tiennm99/gomoku/server/consts"
"github.com/tiennm99/gomoku/server/database"
)
// create handles room creation. Prompts for game type, creates the room, and
+4 -4
View File
@@ -4,10 +4,10 @@ import (
"fmt"
"strings"
"github.com/ratel-online/core/log"
"github.com/ratel-online/core/util/json"
"github.com/ratel-online/server/consts"
"github.com/ratel-online/server/database"
"github.com/tiennm99/gomoku/server/pkg/log"
"github.com/tiennm99/gomoku/server/pkg/json"
"github.com/tiennm99/gomoku/server/consts"
"github.com/tiennm99/gomoku/server/database"
)
// Gomoku state constants for channel-based turn sync.
+2 -2
View File
@@ -2,8 +2,8 @@ package state
import (
"bytes"
"github.com/ratel-online/server/consts"
"github.com/ratel-online/server/database"
"github.com/tiennm99/gomoku/server/consts"
"github.com/tiennm99/gomoku/server/database"
)
// home is the main menu. Player chooses to join an existing room or create a new one.
+2 -2
View File
@@ -3,8 +3,8 @@ package state
import (
"bytes"
"fmt"
"github.com/ratel-online/server/consts"
"github.com/ratel-online/server/database"
"github.com/tiennm99/gomoku/server/consts"
"github.com/tiennm99/gomoku/server/database"
"strconv"
)
+5 -5
View File
@@ -3,11 +3,11 @@ package state
import (
"strings"
"github.com/ratel-online/core/log"
"github.com/ratel-online/core/util/async"
"github.com/ratel-online/server/consts"
"github.com/ratel-online/server/database"
"github.com/ratel-online/server/state/game"
"github.com/tiennm99/gomoku/server/pkg/log"
"github.com/tiennm99/gomoku/server/pkg/async"
"github.com/tiennm99/gomoku/server/consts"
"github.com/tiennm99/gomoku/server/database"
"github.com/tiennm99/gomoku/server/state/game"
)
var states = map[consts.StateID]State{}
+4 -4
View File
@@ -7,10 +7,10 @@ import (
"strings"
"time"
"github.com/ratel-online/core/log"
"github.com/ratel-online/server/consts"
"github.com/ratel-online/server/database"
"github.com/ratel-online/server/state/game"
"github.com/tiennm99/gomoku/server/pkg/log"
"github.com/tiennm99/gomoku/server/consts"
"github.com/tiennm99/gomoku/server/database"
"github.com/tiennm99/gomoku/server/state/game"
)
type waiting struct{}
+3 -3
View File
@@ -3,8 +3,8 @@ package state
import (
"bytes"
"fmt"
"github.com/ratel-online/server/consts"
"github.com/ratel-online/server/database"
"github.com/tiennm99/gomoku/server/consts"
"github.com/tiennm99/gomoku/server/database"
)
// welcome is the initial state. Sends a greeting and transitions to home.
@@ -12,7 +12,7 @@ type welcome struct{}
func (*welcome) Next(player *database.Player) (consts.StateID, error) {
buf := bytes.Buffer{}
buf.WriteString(fmt.Sprintf("Hi %s, Welcome to ratel online! rules at https://github.com/ratel-online/server/blob/main/README.md\n", player.Name))
buf.WriteString(fmt.Sprintf("Hi %s, Welcome to ratel online! rules at https://github.com/tiennm99/gomoku/server/blob/main/README.md\n", player.Name))
err := player.WriteString(buf.String())
if err != nil {
return 0, player.WriteError(err)