From 3be3a34e6e1735bc6f2cafba99f809da5c4f1732 Mon Sep 17 00:00:00 2001 From: wowlikon Date: Mon, 24 Nov 2025 14:08:41 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=BC=D1=83=D0=BB=D1=8C=D1=82=D0=B8=D0=BF?= =?UTF-8?q?=D0=BB=D0=B5=D0=B5=D1=80=D0=B0=20=D0=B8=20dockerfile?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .dockerignore | 3 -- Dockerfile | 42 ++++++++++++++++++++++++++ README.md | 4 +-- generate.py | 41 +++++++++++++++++++++++++ go.mod | 1 + go.sum | 2 ++ server/main.go | 82 ++++++++++++++++++++++++++++++++++++++++++-------- 7 files changed, 157 insertions(+), 18 deletions(-) delete mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 07a1c07..0000000 --- a/.dockerignore +++ /dev/null @@ -1,3 +0,0 @@ -.git -.gitignore -static \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index e69de29..cd293b4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -0,0 +1,42 @@ +# ============================ +# Стадия 1: Сборка +# ============================ +FROM python:3.12-slim AS builder + +# Устанавливаем Go +RUN apt-get update && \ + apt-get install -y --no-install-recommends golang-go && \ + rm -rf /var/lib/apt/lists/* + +# Копируем проект +WORKDIR /app +COPY . . + +# Генерация кода +RUN python generate.py server ./server/toe/ + +# Статическая сборка Go +ENV CGO_ENABLED=0 \ + GOOS=linux \ + GOARCH=amd64 + +# Если ты на Mac M1/M2/M3 — ставь arm64: +# ENV GOARCH=arm64 + +RUN go build -o /app/main ./server/main.go + + +# ============================ +# Стадия 2: Запуск +# ============================ +FROM debian:bookworm-slim + +# Копируем бинарник +COPY --from=builder /app/main /app/main + +# Делаем исполняемым +RUN chmod +x /app/main + +WORKDIR /app + +CMD ["/app/main"] diff --git a/README.md b/README.md index 0122c13..7f00d78 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ ![logo](./logo.png) -# oxTicTacToe Generator +# oxTicTacToe ## Описание @@ -16,7 +16,7 @@ ## Требования -- Python 3.x +- Python 3.12 - Golang 1.25 ## Установка diff --git a/generate.py b/generate.py index b6b701c..25a4b9a 100644 --- a/generate.py +++ b/generate.py @@ -56,6 +56,46 @@ document.addEventListener('mouseover', e => { } }); """) +websocket = minify(""" +(function() { + const ws = new WebSocket(`${window.location.origin.replace(/^http(s?)/, 'ws$1')}/ws${window.location.search}`); + ws.onopen = function(event) { + console.log('WebSocket connected'); + }; + ws.onmessage = function(event) { + console.log('Received:', event.data); + if (document.getElementsByName("g")[0].src != event.data) { + document.getElementsByName("g")[0].src = event.data; + } + }; + ws.onclose = function(event) { + console.log('WebSocket disconnected:', event.code, event.reason); + }; + ws.onerror = function(error) { + console.error('WebSocket error:', error); + }; + window.wsSend = function(text) { + if (ws.readyState === WebSocket.OPEN) { + ws.send(text); + console.log('Sent:', text); + } else { + console.error('WebSocket not connected. Current status:', ws.readyState); + } + }; +})(); + +document.addEventListener('DOMContentLoaded', function() { + const iframe = document.getElementsByName("g")[0]; + if (iframe) { + iframe.addEventListener('load', () => { + iframe.contentDocument.querySelectorAll(`a[href="../../../multiplayer"]`).forEach(link => link.remove()); + wsSend(iframe.contentWindow.location.href); + }); + } else { + console.error('Iframe with name "g" not found.'); + } +}); +""") pages = { 'index': minify(f""" @@ -69,6 +109,7 @@ pages = { scripts = { 's': apply_css, + 'w': websocket, 'p': preload, 'a': (apply_css+preload) } diff --git a/go.mod b/go.mod index 1eaa642..6a7bf81 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/gin-contrib/gzip v1.2.5 github.com/gin-gonic/gin v1.11.0 github.com/google/uuid v1.6.0 + github.com/gorilla/websocket v1.5.3 ) require ( diff --git a/go.sum b/go.sum index faa0e78..88f9ce9 100644 --- a/go.sum +++ b/go.sum @@ -34,6 +34,8 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +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/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= diff --git a/server/main.go b/server/main.go index cef1774..b4a5904 100644 --- a/server/main.go +++ b/server/main.go @@ -11,11 +11,14 @@ import ( "github.com/gin-contrib/gzip" "github.com/gin-gonic/gin" "github.com/google/uuid" + "github.com/gorilla/websocket" ) //go:embed toe var staticFiles embed.FS +var waitingPlayers = make(map[string][]*websocket.Conn) + func SetCustomContentType() gin.HandlerFunc { return func(c *gin.Context) { requestPath := c.Request.URL.Path @@ -35,6 +38,70 @@ func SetCustomContentType() gin.HandlerFunc { } } +func multiplayerHandler(c *gin.Context) { + if c.GetHeader("Accept") == "*/*" { + c.String(http.StatusTeapot, "Preload not allowed") + } + if c.Query("g") == "" { + u, err := uuid.NewV7() + if err != nil { + fmt.Println("Error:", err) + return + } + c.Redirect(http.StatusPermanentRedirect, "/multiplayer?g="+u.String()) + } + c.Header("Content-Type", "text/html; charset=UTF-8") + c.String(http.StatusOK, "") +} + +var upgrader = websocket.Upgrader{ + CheckOrigin: func(r *http.Request) bool { + origin := r.Header.Get("Origin") + host := r.Host + return origin == "http://"+host || origin == "https://"+host + }, +} + +func wsHandler(c *gin.Context) { + game_id := c.Query("g") + if game_id == "" { + c.String(http.StatusTeapot, "No game id") + return + } + + conn, err := upgrader.Upgrade(c.Writer, c.Request, nil) + if err != nil { + fmt.Println("Upgrade error:", err) + return + } + fmt.Println("New client connected") + + waitingPlayers[game_id] = append(waitingPlayers[game_id], conn) + defer func() { + conn.Close() + fmt.Println("Client disconnected") + }() + + for { + messageType, message, err := conn.ReadMessage() + if err != nil { + fmt.Println("Read error:", err) + break + } + fmt.Printf("Received: %s\n", message) + + // Рассылка всем клиентам + for _, c := range waitingPlayers[game_id] { + if c != conn { + err := c.WriteMessage(messageType, message) + if err != nil { + fmt.Println("Broadcast error:", err) + } + } + } + } +} + var favicon = "" func main() { @@ -49,19 +116,8 @@ func main() { router.GET("/favicon.ico", func(c *gin.Context) { c.String(http.StatusOK, favicon) }) - router.GET("/multiplayer", multiplayer) + router.GET("/ws", wsHandler) + router.GET("/multiplayer", multiplayerHandler) router.StaticFS("/tic/tac", embeddedFilesSystem) router.Run(":8080") } - -func multiplayer(c *gin.Context) { - if c.GetHeader("Accept") == "*/*" { - c.String(http.StatusTeapot, "Preload not allowed") - } - u, err := uuid.NewV7() - if err != nil { - fmt.Println("Ошибка:", err) - return - } - c.String(http.StatusOK, fmt.Sprintf("ToDo\nUUID7: %s", u.String())) -}