diff --git a/server/Makefile b/server/Makefile index cdaa20b..d93f02e 100644 --- a/server/Makefile +++ b/server/Makefile @@ -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未运行" \ No newline at end of file + go test ./... diff --git a/server/consts/const.go b/server/consts/const.go index e343946..df64c09 100644 --- a/server/consts/const.go +++ b/server/consts/const.go @@ -3,7 +3,7 @@ package consts import ( "time" - "github.com/ratel-online/core/consts" + "github.com/tiennm99/gomoku/server/pkg/consts" ) type StateID int diff --git a/server/database/database.go b/server/database/database.go index 0642b11..ddaff2e 100644 --- a/server/database/database.go +++ b/server/database/database.go @@ -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. diff --git a/server/database/gomoku.go b/server/database/gomoku.go index ec0670d..d48df06 100644 --- a/server/database/gomoku.go +++ b/server/database/gomoku.go @@ -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. diff --git a/server/database/model.go b/server/database/model.go index 978f43d..33f599b 100644 --- a/server/database/model.go +++ b/server/database/model.go @@ -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. diff --git a/server/go.mod b/server/go.mod index ac8cacb..8a6a244 100644 --- a/server/go.mod +++ b/server/go.mod @@ -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 diff --git a/server/go.sum b/server/go.sum index 45e2ce1..79b174c 100644 --- a/server/go.sum +++ b/server/go.sum @@ -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= diff --git a/server/main.go b/server/main.go index fd80d67..2e2508e 100644 --- a/server/main.go +++ b/server/main.go @@ -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()) } \ No newline at end of file diff --git a/server/network/network.go b/server/network/network.go index 5865194..4dda5dd 100644 --- a/server/network/network.go +++ b/server/network/network.go @@ -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" ) diff --git a/server/network/wss.go b/server/network/wss.go index 688aa22..e22564a 100644 --- a/server/network/wss.go +++ b/server/network/wss.go @@ -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) + } } diff --git a/server/pkg/async/async.go b/server/pkg/async/async.go new file mode 100644 index 0000000..9dabc28 --- /dev/null +++ b/server/pkg/async/async.go @@ -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()) +} diff --git a/server/pkg/consts/consts.go b/server/pkg/consts/consts.go new file mode 100644 index 0000000..3663d39 --- /dev/null +++ b/server/pkg/consts/consts.go @@ -0,0 +1,7 @@ +package consts + +const ( + IsStart = "INTERACTIVE_SIGNAL_START" + IsStop = "INTERACTIVE_SIGNAL_STOP" + MaxPacketSize = 65536 +) diff --git a/server/pkg/json/json.go b/server/pkg/json/json.go new file mode 100644 index 0000000..1621233 --- /dev/null +++ b/server/pkg/json/json.go @@ -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) +} diff --git a/server/pkg/log/log.go b/server/pkg/log/log.go new file mode 100644 index 0000000..0da5982 --- /dev/null +++ b/server/pkg/log/log.go @@ -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) +} diff --git a/server/pkg/model/model.go b/server/pkg/model/model.go new file mode 100644 index 0000000..0f90007 --- /dev/null +++ b/server/pkg/model/model.go @@ -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"` +} diff --git a/server/pkg/network/conn.go b/server/pkg/network/conn.go new file mode 100644 index 0000000..7ab4972 --- /dev/null +++ b/server/pkg/network/conn.go @@ -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() +} diff --git a/server/pkg/protocol/protocol.go b/server/pkg/protocol/protocol.go new file mode 100644 index 0000000..a7abc60 --- /dev/null +++ b/server/pkg/protocol/protocol.go @@ -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 +} diff --git a/server/pkg/protocol/tcp.go b/server/pkg/protocol/tcp.go new file mode 100644 index 0000000..a302148 --- /dev/null +++ b/server/pkg/protocol/tcp.go @@ -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() +} diff --git a/server/pkg/protocol/websocket.go b/server/pkg/protocol/websocket.go new file mode 100644 index 0000000..359cc1e --- /dev/null +++ b/server/pkg/protocol/websocket.go @@ -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() +} diff --git a/server/pkg/strings/strings.go b/server/pkg/strings/strings.go new file mode 100644 index 0000000..f16fb3e --- /dev/null +++ b/server/pkg/strings/strings.go @@ -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) +} diff --git a/server/state/create.go b/server/state/create.go index 5d6b188..255d916 100644 --- a/server/state/create.go +++ b/server/state/create.go @@ -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 diff --git a/server/state/game/gomoku.go b/server/state/game/gomoku.go index 177b40c..ecbed47 100644 --- a/server/state/game/gomoku.go +++ b/server/state/game/gomoku.go @@ -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. diff --git a/server/state/home.go b/server/state/home.go index cdfbeb5..d58c240 100644 --- a/server/state/home.go +++ b/server/state/home.go @@ -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. diff --git a/server/state/join.go b/server/state/join.go index edd294f..839defc 100644 --- a/server/state/join.go +++ b/server/state/join.go @@ -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" ) diff --git a/server/state/state.go b/server/state/state.go index 85dedc7..147faa9 100644 --- a/server/state/state.go +++ b/server/state/state.go @@ -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{} diff --git a/server/state/waiting.go b/server/state/waiting.go index 5512aeb..d58f30c 100644 --- a/server/state/waiting.go +++ b/server/state/waiting.go @@ -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{} diff --git a/server/state/welcome.go b/server/state/welcome.go index 11e77f3..8e816aa 100644 --- a/server/state/welcome.go +++ b/server/state/welcome.go @@ -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)