Добавление мультиплеера и dockerfile
This commit is contained in:
@@ -1,3 +0,0 @@
|
|||||||
.git
|
|
||||||
.gitignore
|
|
||||||
static
|
|
||||||
42
Dockerfile
42
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"]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||

|

|
||||||
# oxTicTacToe Generator
|
# oxTicTacToe
|
||||||
|
|
||||||
## Описание
|
## Описание
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
## Требования
|
## Требования
|
||||||
|
|
||||||
- Python 3.x
|
- Python 3.12
|
||||||
- Golang 1.25
|
- Golang 1.25
|
||||||
|
|
||||||
## Установка
|
## Установка
|
||||||
|
|||||||
41
generate.py
41
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 = {
|
pages = {
|
||||||
'index':
|
'index':
|
||||||
minify(f"""<meta name="viewport" content="width=device-width,initial-scale=1">
|
minify(f"""<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
@@ -69,6 +109,7 @@ pages = {
|
|||||||
|
|
||||||
scripts = {
|
scripts = {
|
||||||
's': apply_css,
|
's': apply_css,
|
||||||
|
'w': websocket,
|
||||||
'p': preload,
|
'p': preload,
|
||||||
'a': (apply_css+preload)
|
'a': (apply_css+preload)
|
||||||
}
|
}
|
||||||
|
|||||||
1
go.mod
1
go.mod
@@ -6,6 +6,7 @@ require (
|
|||||||
github.com/gin-contrib/gzip v1.2.5
|
github.com/gin-contrib/gzip v1.2.5
|
||||||
github.com/gin-gonic/gin v1.11.0
|
github.com/gin-gonic/gin v1.11.0
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
|
github.com/gorilla/websocket v1.5.3
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
|||||||
2
go.sum
2
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/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 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
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 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||||
|
|||||||
@@ -11,11 +11,14 @@ import (
|
|||||||
"github.com/gin-contrib/gzip"
|
"github.com/gin-contrib/gzip"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed toe
|
//go:embed toe
|
||||||
var staticFiles embed.FS
|
var staticFiles embed.FS
|
||||||
|
|
||||||
|
var waitingPlayers = make(map[string][]*websocket.Conn)
|
||||||
|
|
||||||
func SetCustomContentType() gin.HandlerFunc {
|
func SetCustomContentType() gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
requestPath := c.Request.URL.Path
|
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, "<iframe name=\"g\" src=\"/\" width=\"100%\" height=\"100%\"></iframe><script src=\"/tic/tac/toe/w.js\"></script>")
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = "<svg width=\"32\" height=\"32\" viewBox=\"0 0 32 32\" xmlns=\"http://www.w3.org/2000/svg\"><path fill=\"none\" stroke=\"#000\" d=\"M10 10h12v12H10z\"/><path d=\"M22 22a8 8 0 0 0-12-12\" fill=\"none\" stroke=\"#f30\" stroke-width=\"2\"/><path stroke=\"#07f\" stroke-width=\"2\" d=\"m8 8 16 16M8 24l8-8\"/><path stroke=\"#000\" d=\"m8.5 7.5 16 16\"/></svg>"
|
var favicon = "<svg width=\"32\" height=\"32\" viewBox=\"0 0 32 32\" xmlns=\"http://www.w3.org/2000/svg\"><path fill=\"none\" stroke=\"#000\" d=\"M10 10h12v12H10z\"/><path d=\"M22 22a8 8 0 0 0-12-12\" fill=\"none\" stroke=\"#f30\" stroke-width=\"2\"/><path stroke=\"#07f\" stroke-width=\"2\" d=\"m8 8 16 16M8 24l8-8\"/><path stroke=\"#000\" d=\"m8.5 7.5 16 16\"/></svg>"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -49,19 +116,8 @@ func main() {
|
|||||||
router.GET("/favicon.ico", func(c *gin.Context) {
|
router.GET("/favicon.ico", func(c *gin.Context) {
|
||||||
c.String(http.StatusOK, favicon)
|
c.String(http.StatusOK, favicon)
|
||||||
})
|
})
|
||||||
router.GET("/multiplayer", multiplayer)
|
router.GET("/ws", wsHandler)
|
||||||
|
router.GET("/multiplayer", multiplayerHandler)
|
||||||
router.StaticFS("/tic/tac", embeddedFilesSystem)
|
router.StaticFS("/tic/tac", embeddedFilesSystem)
|
||||||
router.Run(":8080")
|
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()))
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user