diff --git a/app.js b/app.js index 9d8fc7e..a419abf 100644 --- a/app.js +++ b/app.js @@ -349,6 +349,40 @@ io.on("connection", (socket) => { } }); + /* ── 1v1: Bereit-System ── */ + if (!io._arenaReady) io._arenaReady = new Map(); // matchId → Set of ready slots + + socket.on("player_ready", (data) => { + const { matchId, slot } = data; + if (!matchId || !slot) return; + + if (!io._arenaReady.has(matchId)) { + io._arenaReady.set(matchId, new Set()); + } + + const readySet = io._arenaReady.get(matchId); + readySet.add(slot); + + // Beide Spieler in der Arena-Room benachrichtigen + io.to("arena_" + matchId).emit("ready_status", { + readyCount: readySet.size, + }); + + console.log(`[1v1] ${slot} ist bereit in Match ${matchId} (${readySet.size}/2)`); + + // Aufräumen wenn beide bereit + if (readySet.size >= 2) { + io._arenaReady.delete(matchId); + } + }); + + socket.on("player_surrender", (data) => { + const { matchId, slot } = data; + console.log(`[1v1] ${slot} hat aufgegeben in Match ${matchId}`); + // Aufgabe-Logik kommt hier rein + io.to("arena_" + matchId).emit("player_surrendered", { slot }); + }); + /* ── 1v1: Spielfeld-Verbindung (beide Spieler im iframe) ── */ // Map: matchId → { player1: socketId, player2: socketId, names: {} } if (!io._arenaRooms) io._arenaRooms = new Map(); diff --git a/public/css/1v1.css b/public/css/1v1.css index dd86183..ba5355f 100644 --- a/public/css/1v1.css +++ b/public/css/1v1.css @@ -474,3 +474,122 @@ body { border-color: rgba(255, 215, 80, 0.8); transform: scale(1.1); } + +/* ── Verbindungs-Overlay ── */ +#connecting-overlay { + position: fixed; + inset: 0; + z-index: 200; + background: rgba(0,0,0,0.85); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + font-family: "Cinzel", serif; + color: rgba(255,215,80,0.85); + font-size: 18px; + letter-spacing: 3px; +} +#connecting-overlay p { + margin-top: 12px; + font-size: 12px; + color: rgba(255,255,255,0.4); + letter-spacing: 2px; +} +@keyframes spin { + to { transform: rotate(360deg); } +} +#connecting-overlay .spinner { + width: 40px; + height: 40px; + border: 3px solid rgba(255,215,80,0.2); + border-top-color: #ffd750; + border-radius: 50%; + animation: spin 0.8s linear infinite; + margin-bottom: 16px; +} + +/* ── Avatar Name (immer sichtbar) ── */ +.av-name { + position: absolute; + bottom: 8px; + left: 0; + right: 0; + text-align: center; + font-family: "Cinzel", serif; + font-size: 13px; + font-weight: 700; + color: #ffd750; + text-shadow: 0 1px 6px rgba(0,0,0,0.9), 0 0 12px rgba(0,0,0,0.7); + letter-spacing: 1px; + pointer-events: none; + z-index: 10; + padding: 0 4px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +/* ── Top Bar Actions (Buttons-Gruppe) ── */ +.top-bar-actions { + display: flex; + align-items: center; + gap: calc(var(--s) * 10); +} + +/* BEREIT Button */ +.bereit-btn { + background: linear-gradient(135deg, #1a7a1a, #27ae60); + border: 2px solid rgba(100, 255, 100, 0.5); + border-radius: calc(var(--s) * 8); + color: #fff; + font-family: "Cinzel", serif; + font-size: calc(var(--s) * 12); + letter-spacing: calc(var(--s) * 2); + padding: calc(var(--s) * 7) calc(var(--s) * 20); + cursor: pointer; + text-transform: uppercase; + box-shadow: 0 0 calc(var(--s) * 14) rgba(0, 200, 0, 0.4); + transition: all 0.2s; +} +.bereit-btn:hover:not(:disabled) { + background: linear-gradient(135deg, #22992a, #2ecc71); + box-shadow: 0 0 calc(var(--s) * 24) rgba(0, 200, 0, 0.7); +} +.bereit-btn.bereit-clicked { + background: linear-gradient(135deg, #145214, #1e8449); + border-color: rgba(100, 255, 100, 0.3); + opacity: 0.75; + cursor: default; +} + +/* AUFGEBEN Button */ +.aufgeben-btn { + background: linear-gradient(135deg, #2c2c2c, #444); + border: 2px solid rgba(255, 255, 255, 0.35); + border-radius: calc(var(--s) * 8); + color: #fff; + font-family: "Cinzel", serif; + font-size: calc(var(--s) * 12); + letter-spacing: calc(var(--s) * 2); + padding: calc(var(--s) * 7) calc(var(--s) * 20); + cursor: pointer; + text-transform: uppercase; + box-shadow: 0 0 calc(var(--s) * 8) rgba(0, 0, 0, 0.5); + transition: all 0.2s; +} +.aufgeben-btn:hover { + background: linear-gradient(135deg, #3a3a3a, #555); + border-color: rgba(255, 255, 255, 0.7); + box-shadow: 0 0 calc(var(--s) * 16) rgba(255, 255, 255, 0.2); +} + +/* Spielfeld-Sperre */ +#board-lock-overlay { + position: absolute; + inset: 0; + z-index: 50; + background: rgba(0, 0, 0, 0.55); + backdrop-filter: blur(2px); + cursor: not-allowed; +} diff --git a/views/1v1_spielfeld.ejs b/views/1v1_spielfeld.ejs index 6cc2497..fcac459 100644 --- a/views/1v1_spielfeld.ejs +++ b/views/1v1_spielfeld.ejs @@ -11,107 +11,10 @@ /> - - - -
- ⚔️ 1v1 KAMPFARENA -
-
- Verbinde… -
- Match: <%= matchId || "—" %> -
-
@@ -131,9 +34,16 @@
🏆
- +
+ + + +
+ +
+
{ const overlay = document.getElementById("connecting-overlay"); if (overlay) overlay.remove(); - document.getElementById("mb-status-text").textContent = "Verbunden · Kampf läuft"; // Namen setzen document.getElementById("nameLeft").textContent = data.player1 || "Spieler 1"; document.getElementById("nameRight").textContent = data.player2 || "Spieler 2"; }); + // ── Bereit-System ───────────────────────────────────────────────────── + let myReady = false; + + function handleBereit() { + if (myReady) return; + myReady = true; + + const btn = document.getElementById("bereit-btn"); + btn.textContent = "✔ BEREIT"; + btn.classList.add("bereit-clicked"); + btn.disabled = true; + + socket.emit("player_ready", { matchId, slot: mySlot }); + } + + socket.on("ready_status", (data) => { + // data.readyCount = wie viele Spieler bereits bereit sind + if (data.readyCount === 2) { + // Beide bereit → Sperre aufheben + const lock = document.getElementById("board-lock-overlay"); + if (lock) lock.remove(); + + const bereitBtn = document.getElementById("bereit-btn"); + if (bereitBtn) bereitBtn.remove(); + + const endBtn = document.getElementById("end-turn-btn"); + if (endBtn) endBtn.disabled = false; + } + }); + + function handleAufgeben() { + // Funktion offen – hier kann später die Aufgabe-Logik rein + socket.emit("player_surrender", { matchId, slot: mySlot }); + } + // ───────────────────────────────────────────────────────────────────── + // ── Avatar laden (lokal) ────────────────────────────────────────────── function loadAvatar(input, imgId, parentId) { const file = input.files[0];