diff --git a/.gitignore b/.gitignore index ecdef5b..4740311 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -static \ No newline at end of file +static +server/toe diff --git a/generate.py b/generate.py index adf273a..b6b701c 100644 --- a/generate.py +++ b/generate.py @@ -13,8 +13,8 @@ if help_mode: print(f'Args:') print(f'\thelp - Show this message') print(f'\tfile - File mode with .html extension') - print(f'\tserver - Server mode without .html extension for use folder as static files') - print(f'\t[path]/ - Directory for save files') + print(f'\tserver - Server mode without .html extension for use folder as static pages') + print(f'\t[path]/ - Directory for save pages') exit(0) else: if file_mode and server_mode: @@ -39,12 +39,39 @@ def check_win(board) -> str: return next((board[line[0]] for line in lines if board[line[0]] == board[line[1]] == board[line[2]] != '-'), '-') minify = lambda s: s.replace('\n', '').replace('\t', '').replace(' ', '') -files = { - 'index': - minify(f"""{'' if server_mode else ''}oxTicTacToe -

oxTicTacToe.html

Select team for start [ O | X ]

- source code""") +style = minify(""" +body{margin:0;padding:20px;font-family:Arial,sans-serif;text-align:center;background:#fff}h1{font-size:24px;margin-bottom:20px} +a{text-decoration:none}img{width:10vw}a,h1 a{color:#007bff}table{margin:0 auto;border-collapse:collapse;max-width:100%;overflow-x:auto} +td,td a{line-height:50px}td{border:1px solid #ccc;padding:10px;width:50px;height:50px;text-align:center;font-size:18px;vertical-align:middle} +td a{display:block;width:100%;height:100%;color:#007bff}td:hover{background:#eee}@media (max-width:600px){body{padding:10px}img{width:20vw} +h1{font-size:20px;margin-bottom:10px}td{padding:5px;width:40px;height:40px;font-size:16px}td,td a{line-height:40px}}""") +apply_css = minify(f"const style=document.createElement('style');style.textContent='{style}';document.head.appendChild(style);") +preload = minify(""" +document.addEventListener('mouseover', e => { + const a = e.target.closest('a[href]'); + if (!a) return; + if (!a.dataset.prefetched) { + a.dataset.prefetched = true; + fetch(a.href, {mode: 'no-cors'}); } +}); +""") +pages = { + 'index': + minify(f""" + + oxTicTacToe

oxTicTacToe.html

+

Select team for start [ O | X ] + {'
multiplayer mode' if server_mode else ''}

+ source code + """) +} + +scripts = { + 's': apply_css, + 'p': preload, + 'a': (apply_css+preload) +} try: iterable = tqdm(itertools.product('-xo', repeat=10)) except: iterable = itertools.product('-xo', repeat=10) @@ -61,8 +88,8 @@ for field_tuple in iterable: line = lambda n: "index.html" title = f'Draft' else: title = field[0] - content = minify(f"""{'' if server_mode else ''} - oxTTT:{title}

{title}

+ content = minify(f""" + oxTTT:{title}

{title}

@@ -79,22 +106,18 @@ for field_tuple in iterable: -
{'' if field[1] != '-' else f''}{field[1].replace('-', '□')}{'' if field[1] != '-' else ''}{'' if field[8] != '-' else f''}{field[8].replace('-', '□')}{'' if field[8] != '-' else ''} {'' if field[9] != '-' else f''}{field[9].replace('-', '□')}{'' if field[9] != '-' else ''}
""") - files[field] = f"{'' if win != '-' else ''}{content}{'' if win != '-' else ''}" + """) + pages[field] = f"{'' if win != '-' else ''}{content}{'' if win != '-' else ''}" os.makedirs(path, exist_ok=True) -for filename, content in files.items(): +for filename, content in pages.items(): with open(f'{path}{filename}{".html" if file_mode else ""}', 'w+', encoding='utf-8') as f: f.write(('' if server_mode else '')+(content.replace('.html', '') if server_mode else content)) -with open(f'{path}s.js', 'w+', encoding='utf-8') as f: - f.write(minify(""" - const style=document.createElement('style'); - style.textContent=' - body{margin:0;padding:20px;font-family:Arial,sans-serif;text-align:center} - h1{font-size:24px;margin-bottom:20px}a{text-decoration:none;color:#007bff} - h1 a{color:#007bff}table{margin:0 auto;border-collapse:collapse} - td{border:1px solid #ccc;padding:10px;width:50px;height:50px; - text-align:center;font-size:18px;line-height:50px;vertical-align:middle} - td a{display:block;width:100%;height:100%;line-height:50px;color:#007bff} - td:hover{background:#f0f0f0}';document.head.appendChild(style);""")) \ No newline at end of file + +for filename, content in scripts.items(): + with open(f'{path}{filename}.js', 'w+', encoding='utf-8') as f: + f.write(content) + +with open(f'{path}s.css', 'w+', encoding='utf-8') as f: + f.write(style) diff --git a/go.mod b/go.mod index 832ec82..1eaa642 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.25.3 require ( github.com/gin-contrib/gzip v1.2.5 github.com/gin-gonic/gin v1.11.0 + github.com/google/uuid v1.6.0 ) require ( diff --git a/go.sum b/go.sum index 5226eb0..faa0e78 100644 --- a/go.sum +++ b/go.sum @@ -32,6 +32,8 @@ github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7Lk github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 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/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 a6cff45..cef1774 100644 --- a/server/main.go +++ b/server/main.go @@ -4,35 +4,64 @@ import ( // "flag" // "fmt" "embed" + "fmt" "net/http" "strings" "github.com/gin-contrib/gzip" "github.com/gin-gonic/gin" + "github.com/google/uuid" ) -//go:embed static +//go:embed toe var staticFiles embed.FS func SetCustomContentType() gin.HandlerFunc { - return func(c *gin.Context) { + return func(c *gin.Context) { requestPath := c.Request.URL.Path - if strings.HasPrefix(requestPath, "/l/static") { - c.Header("Content-Type", "text/html; charset=UTF-8") + if strings.Contains(requestPath, ".") { + c.Header("Cache-Control", "public, max-age=31536000, immutable") } - c.Next() - } + if strings.HasPrefix(requestPath, "/tic/tac/toe") && !strings.Contains(requestPath, ".") { + c.Header("Content-Type", "text/html; charset=UTF-8") + } else if strings.HasSuffix(requestPath, ".ico") { + c.Header("Content-Type", "image/svg+xml") + } else if strings.HasSuffix(requestPath, ".js") { + c.Header("Content-Type", "application/javascript") + } else if strings.HasSuffix(requestPath, ".css") { + c.Header("Content-Type", "text/css") + } + c.Next() + } } +var favicon = "" + func main() { embeddedFilesSystem := http.FS(staticFiles) router := gin.Default() router.Use(SetCustomContentType()) router.Use(gzip.Gzip(gzip.DefaultCompression)) - router.GET("/ping", func(c *gin.Context) { - c.JSON(200, gin.H{"message": "pong"}) - }) - router.StaticFS("/l", embeddedFilesSystem) - router.Run(":8080") -} \ No newline at end of file + router.GET("/", func(c *gin.Context) { + c.Redirect(http.StatusMovedPermanently, "/tic/tac/toe/index") + }) + router.GET("/favicon.ico", func(c *gin.Context) { + c.String(http.StatusOK, favicon) + }) + router.GET("/multiplayer", multiplayer) + 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())) +}