summaryrefslogtreecommitdiff
path: root/server
diff options
context:
space:
mode:
authorewy <ewy0@protonmail.com>2026-09-22 21:29:39 +0200
committerewy <ewy0@protonmail.com>2026-09-22 21:29:39 +0200
commite57ab3ca3907ae186bae1af088ca880ae880c312 (patch)
tree97015e4b13de8faee8a172f7bc306a8fa66355c2 /server
parent283f6e19741b262c46a8a2ba3f7ba81f01519d70 (diff)
progress
Diffstat (limited to 'server')
-rw-r--r--server/api/game.go13
-rw-r--r--server/web/game/game.js67
-rw-r--r--server/web/game/index.html69
-rw-r--r--server/web/game/style.css160
4 files changed, 237 insertions, 72 deletions
diff --git a/server/api/game.go b/server/api/game.go
index d830d71..e6235b9 100644
--- a/server/api/game.go
+++ b/server/api/game.go
@@ -1,6 +1,7 @@
package api
import (
+ "fmt"
"net/http"
"sakura/data/cirno"
"sakura/engine"
@@ -66,11 +67,9 @@ func GameConnect(w http.ResponseWriter, r *http.Request) {
func Bind(g *engine.Game, w http.ResponseWriter, r *http.Request) (*engine.Player, *http.Cookie) {
cookie, err := r.Cookie("User")
- newUser := false
var id string
if err != nil {
- newUser = true
uid := uuid.NewV7()
id = uid.String()
cookie = &http.Cookie{
@@ -88,10 +87,10 @@ func Bind(g *engine.Game, w http.ResponseWriter, r *http.Request) (*engine.Playe
}
switch {
- case newUser && g.Yin.Cookie == "":
+ case g.Yin.Cookie == "" && g.Yang.Cookie != id:
g.Yin.Cookie = id
return g.Yin, cookie
- case newUser && g.Yang.Cookie == "":
+ case g.Yang.Cookie == "" && g.Yin.Cookie != id:
g.Yang.Cookie = id
return g.Yang, cookie
case g.Yang.Cookie == id:
@@ -106,7 +105,11 @@ func Bind(g *engine.Game, w http.ResponseWriter, r *http.Request) (*engine.Playe
func Update(id uuid.UUID, g *engine.Game) {
for _, c := range connections[id] {
- _ = c.Socket.WriteJSON(g)
+ err := c.Socket.WriteJSON(g)
+ if err != nil {
+ fmt.Printf("%v\n", err)
+ }
+
}
}
diff --git a/server/web/game/game.js b/server/web/game/game.js
index 78e5547..05756fc 100644
--- a/server/web/game/game.js
+++ b/server/web/game/game.js
@@ -1,24 +1,59 @@
"use strict";
-const showGame = (g) => {
+
+var cloned = false
+
+const cloneGame = (g) => {
const tmpl = document.querySelector("template#tmpl");
const disp = document.querySelector("#game");
const clone = document.importNode(tmpl.content, true);
- doDistance(clone, g.Distance.Value);
- doActions(clone, g.Actions);
- disp.replaceChildren(clone);
+ const localPlayer = clone.querySelector("#player").content.cloneNode(true)
+ localPlayer.querySelector("div").setAttribute("id", "local-player")
+ const remotePlayer = clone.querySelector("#player").content.cloneNode(true)
+ remotePlayer.querySelector("div").setAttribute("id", "remote-player")
+
+ disp.replaceChildren(remotePlayer, clone, localPlayer);
+ showGame(disp, g)
+}
+
+const showGame = (n, g) => {
+ console.debug(g)
+ doDistance(n, g.Distance.Value);
+ doActions(n, g.Actions);
+ doPlayer(n.querySelector("#local-player"), g[g.You])
+ doPlayer(n.querySelector("#remote-player"), g[g.Them])
};
-const doDistance = (clone, value) => {
- clone.querySelector("#distance").setAttribute("data-value", value);
- [...clone.querySelectorAll("#distance .content .sakura")].map((n, i) => {
- n.checked = i < value;
- });
- clone.querySelector("#distance .pool-value").innerText = value
+const doPlayer = (n, p) => {
+ doPool(n.querySelector(".life"), p.Life.Value)
+ doPool(n.querySelector(".aura"), p.Life.Value)
+
+}
+
+const doPool = (node, value) => {
+ node.setAttribute("data-value", value);
+ node.querySelectorAll(".sakura").forEach((n, i) => {
+ const shouldCheck = (n.getAttribute("data-ordering") ?? i) < value
+ n.checked = shouldCheck
+ n.setAttribute("checked", shouldCheck)
+ })
+ const valueNode = node.querySelector(".pool-value")
+ if (valueNode)
+ valueNode.innerText = value
+}
+
+const doDistance = (node, value) => {
+ const distance = node.querySelector("#distance")
+ doPool(distance, value)
};
+
+let actionTemplate
+
const doActions = (g, actions) => {
- const actionList = g.querySelector(".actions");
- const actionTemplate = actionList.querySelector(".action").cloneNode(true);
+ const actionList = g.querySelector("#actions");
+ if (!actionTemplate) {
+ actionTemplate = document.importNode(actionList.querySelector("template").content.querySelector(".action"), true)
+ }
if (actions) {
actionList.replaceChildren(
...actions.map((a, i) => {
@@ -42,8 +77,12 @@ const connect = (gameId) => {
window.socket = connection;
connection.addEventListener("message", (ev) => {
const game = JSON.parse(ev.data);
- console.debug(game);
- showGame(game);
+ if (cloned) {
+ showGame(document.querySelector("#game"), game)
+ } else {
+ cloneGame(game)
+ cloned = true
+ }
document.querySelector("#game-json").innerText = JSON.stringify(
ev.data,
null,
diff --git a/server/web/game/index.html b/server/web/game/index.html
index 04e85d2..f022c64 100644
--- a/server/web/game/index.html
+++ b/server/web/game/index.html
@@ -18,32 +18,61 @@
<code id="game-json"></code>
</details>
</header>
+<img class="background" src="/game/assets/sea-water-texture-seamless.jpg">
<main id="game"></main>
<template id="tmpl">
- <div id="distance">
- <div class="content">
- <input type="checkbox" class="sakura" style="animation-delay: 0.7s"/>
- <input type="checkbox" class="sakura" style="animation-delay: 1.4s"/>
- <input type="checkbox" class="sakura" style="animation-delay: 2.1s"/>
- <input type="checkbox" class="sakura" style="animation-delay: 2.8s"/>
- <input type="checkbox" class="sakura" style="animation-delay: 3.5s"/>
- <input type="checkbox" class="sakura" style="animation-delay: 4.2s"/>
- <input type="checkbox" class="sakura" style="animation-delay: 4.9s"/>
- <input type="checkbox" class="sakura" style="animation-delay: 5.6s"/>
- <input type="checkbox" class="sakura" style="animation-delay: 6.3s"/>
- <input type="checkbox" class="sakura" style="animation-delay: 7s"/>
+ <div id="arena">
+ <div id="shadow" class="pool">
+ <input type="checkbox" data-ordering="8" class="sakura" style="animation-delay: 0.7s"/>
+ </div>
+ <div id="distance" class="pool">
+ <div class="content">
+ <input type="checkbox" data-ordering="8" class="sakura" style="animation-delay: 0.7s"/>
+ <input type="checkbox" data-ordering="6" class="sakura" style="animation-delay: 1.4s"/>
+ <input type="checkbox" data-ordering="4" class="sakura" style="animation-delay: 2.1s"/>
+ <input type="checkbox" data-ordering="2" class="sakura" style="animation-delay: 2.8s"/>
+ <input type="checkbox" data-ordering="0" class="sakura" style="animation-delay: 3.5s"/>
+ <input type="checkbox" data-ordering="1" class="sakura" style="animation-delay: 4.2s"/>
+ <input type="checkbox" data-ordering="3" class="sakura" style="animation-delay: 4.9s"/>
+ <input type="checkbox" data-ordering="5" class="sakura" style="animation-delay: 5.6s"/>
+ <input type="checkbox" data-ordering="7" class="sakura" style="animation-delay: 6.3s"/>
+ <input type="checkbox" data-ordering="9" class="sakura" style="animation-delay: 7s"/>
+ </div>
</div>
- <label class="label pool-name">Distance</label>
- <label class="label pool-value">10</label>
</div>
- <ul class="actions">
- <li class="action" data-index="">
- <button type="button" class="description"
- onclick="window.socket.send(closest('.action').getAttribute('data-index'))">Do
- </button>
- </li>
+ <ul id="actions">
+ <template>
+ <li class="action" data-index="">
+ <button type="button" class="description"
+ onclick="window.socket.send(closest('.action').getAttribute('data-index'))">Do
+ </button>
+ </li>
+ </template>
</ul>
+ <template id="player">
+ <div class="player">
+ <div class="pool aura" data-value="3">
+ <input type="checkbox" data-ordering="0" class="sakura"/>
+ <input type="checkbox" data-ordering="1" class="sakura"/>
+ <input type="checkbox" data-ordering="2" class="sakura"/>
+ <input type="checkbox" data-ordering="3" class="sakura"/>
+ <input type="checkbox" data-ordering="4" class="sakura"/>
+ </div>
+ <div class="pool life" data-value="3">
+ <input type="checkbox" data-ordering="0" class="sakura"/>
+ <input type="checkbox" data-ordering="1" class="sakura"/>
+ <input type="checkbox" data-ordering="2" class="sakura"/>
+ <input type="checkbox" data-ordering="3" class="sakura"/>
+ <input type="checkbox" data-ordering="4" class="sakura"/>
+ <input type="checkbox" data-ordering="5" class="sakura"/>
+ <input type="checkbox" data-ordering="6" class="sakura"/>
+ <input type="checkbox" data-ordering="7" class="sakura"/>
+ <input type="checkbox" data-ordering="8" class="sakura"/>
+ <input type="checkbox" data-ordering="9" class="sakura"/>
+ </div>
+ </div>
+ </template>
</template>
<script src="/game/game.js"></script>
diff --git a/server/web/game/style.css b/server/web/game/style.css
index 83f06ad..d7baa20 100644
--- a/server/web/game/style.css
+++ b/server/web/game/style.css
@@ -2,58 +2,152 @@
display: none;
}
-#distance {
+#actions {
+ position: absolute;
+ bottom: 0;
+ width: 80%;
+ margin: 0 auto 0 auto;
+ transform: translateY(80%);
+ transition: transform 0.2s ease-in-out;
+ padding: 1em;
+ border: solid 2px black;
+ border-bottom: none;
+ border-radius: 1em 1em 0 0;
+ background-color: slategray;
- & .label {
- position: relative;
+ &:hover {
+ transform: translateY(0);
+ }
+}
+
+.action button {
+ border-color: lightpink;
+ background-color: rgba(41, 24, 24, 0.4);
+ color: floralwhite;
+}
- bottom: 0;
- margin-bottom: 4px;
+.sakura {
+ transition-property: opacity;
+ transition-duration: 0.5s;
+ opacity: 1;
- &.pool-name {
- left: 0;
- }
+ /* noninteractive and style override */
+ pointer-events: none;
+ -webkit-appearance: none;
+ appearance: none;
+ background-color: transparent;
- &.pool-value {
- right: 0;
- }
+ &::before {
+ content: "🌸"
}
- & .content {
- display: flex;
- justify-content: center;
+ font-size: 2em;
+ @starting-style{
+ opacity: 0;
+ }
+}
- & input[type=checkbox] {
- pointer-events: none;
- -webkit-appearance: none;
- appearance: none;
- background-color: transparent;
- margin: 4px;
- font-size: 2em;
+body {
+ overflow: hidden;
+}
- @media (prefers-reduced-motion: no-preference) {
- animation: 5s shake ease-in-out alternate infinite;
+.background {
+ position: absolute;
+ top: 0;
+ height: 100%;
+ background-image: url("/game/assets/sea-water-texture-seamless.jpg");
+ animation: alternate scroll 60s infinite ease-in-out;
+ z-index: -1;
+ filter: contrast(10%);
+ width: 200vw;
+
+}
+
+#arena {
+
+ margin: 0 2em;
+
+ display: flex;
+ flex-direction: row-reverse;
+ justify-content: left;
+
+ & #distance {
+ & .label {
+ position: relative;
+
+ bottom: 0;
+
+ &.pool-name {
+ left: 0;
}
- &::before {
- color: lightseagreen;
- content: "î¼°"
+ &.pool-value {
+ right: 0;
+ }
+ }
+
+ & .sakura {
+ height: 0.6em;
+
+ text-shadow: 0 0 0.5em #70b8da;
+
+ @media (prefers-reduced-motion: no-preference) {
+ animation: 1s sway ease-in-out alternate infinite;
}
- &:checked {
- &::before {
- content: "🌸"
- }
+ &:not(:checked) {
+ filter: grayscale(85%) contrast(25%);
}
}
+
+ & .content {
+ display: flex;
+ justify-content: center;
+ flex-direction: column;
+ }
+ }
+}
+
+@keyframes sway {
+ from {
+ transform: translateY(6%) rotateX(-5deg)
+ }
+ to {
+ transform: translateY(-6%) rotateX(5deg)
}
}
-@keyframes shake {
+@keyframes scroll {
from {
- transform: translateY(0) rotateZ(0deg)
+ transform: translateX(-30%) rotateX(5deg)
}
to {
- transform: translateY(12%) rotateZ(60deg)
+ transform: translateX(-60%) rotateX(-5deg)
}
+}
+
+#remote-player {
+ transform: rotateX(180deg);
+}
+
+.pool {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: space-around;
+
+ &.life {
+ background-color: rgba(139, 0, 0, 0.57);
+ border: solid 0.2em indianred;
+ border-top: none;
+ }
+
+ &.aura {
+ background-color: rgba(173, 216, 230, 0.6);
+ border-radius: 1em 1em 0 0;
+ box-shadow: 0 0 0.4em lightskyblue;
+ }
+}
+
+#shadow {
+ flex-basis: 50%;
} \ No newline at end of file