diff --git a/app.js b/app.js
index b30782c..552ed11 100644
--- a/app.js
+++ b/app.js
@@ -24,12 +24,12 @@ const blackmarket = require("./routes/blackmarket.route");
const mineRoute = require("./routes/mine.route");
const carddeckRoutes = require("./routes/carddeck.route");
const arenaRoutes = require("./routes/arena.route");
-const { registerArenaHandlers } = require("./sockets/arena");
+const { registerArenaHandlers } = require("./sockets/arena.socket");
const { registerChatHandlers } = require("./sockets/chat");
const boosterRoutes = require("./routes/booster.route");
-const pointsRoutes = require("./routes/points.route");
+const pointsRoutes = require("./routes/points.route");
const combineRoutes = require("./routes/combine.route");
-const bazaarRoutes = require("./routes/bazaar.route");
+const bazaarRoutes = require("./routes/bazaar.route");
const compression = require("compression");
@@ -61,11 +61,20 @@ app.use(
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'"],
scriptSrcAttr: ["'unsafe-inline'"],
- styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "https://cdnjs.cloudflare.com"],
- fontSrc: ["'self'", "https://fonts.gstatic.com", "https://cdnjs.cloudflare.com"],
+ styleSrc: [
+ "'self'",
+ "'unsafe-inline'",
+ "https://fonts.googleapis.com",
+ "https://cdnjs.cloudflare.com",
+ ],
+ fontSrc: [
+ "'self'",
+ "https://fonts.gstatic.com",
+ "https://cdnjs.cloudflare.com",
+ ],
imgSrc: ["'self'", "data:", "blob:"],
connectSrc: ["'self'", "ws:", "wss:"],
- frameAncestors: ["'self'"], // Erlaubt iframe von eigener Domain
+ frameAncestors: ["'self'"], // Erlaubt iframe von eigener Domain
},
},
}),
@@ -105,7 +114,7 @@ app.use(
app.set("view engine", "ejs");
app.set("views", path.join(__dirname, "views"));
-const shopRoutes = require("./routes/shop.route");
+const shopRoutes = require("./routes/shop.route");
/* ========================
WICHTIG: Shop/Webhook VOR express.json()
diff --git a/sockets/arena.js b/sockets/arena.js
deleted file mode 100644
index 2f70808..0000000
--- a/sockets/arena.js
+++ /dev/null
@@ -1,199 +0,0 @@
-/* ============================================================
- sockets/arena.js
- Alle Socket-Events rund um 1v1 Matchmaking, Spielfeld & Bereit-System
-============================================================ */
-
-const waitingPool = new Map(); // socketId → { socket, player }
-const LEVEL_RANGE = 5;
-
-// Werden beim ersten Event lazy initialisiert (auf io gespeichert)
-// io._arenaRooms → matchId → { sockets, names }
-// io._arenaReady → matchId → Set of ready slots
-// io._arenaTimers → matchId → intervalId
-
-const READY_TIMEOUT = 30; // Sekunden bis Match abgebrochen wird
-
-function startReadyTimer(io, matchId) {
- if (!io._arenaTimers) io._arenaTimers = new Map();
- if (io._arenaTimers.has(matchId)) return; // läuft bereits
-
- let remaining = READY_TIMEOUT;
-
- // Sofort ersten Tick senden
- io.to("arena_" + matchId).emit("ready_timer", { remaining });
-
- const interval = setInterval(() => {
- remaining--;
- io.to("arena_" + matchId).emit("ready_timer", { remaining });
-
- if (remaining <= 0) {
- clearInterval(interval);
- io._arenaTimers.delete(matchId);
-
- // Match abbrechen – Funktion noch offen
- console.log(`[1v1] Match ${matchId} abgebrochen – Zeit abgelaufen.`);
- io.to("arena_" + matchId).emit("match_cancelled", {
- reason: "timeout",
- message: "Zeit abgelaufen – Match wird abgebrochen.",
- });
- }
- }, 1000);
-
- io._arenaTimers.set(matchId, interval);
-}
-
-function stopReadyTimer(io, matchId) {
- if (!io._arenaTimers) return;
- const interval = io._arenaTimers.get(matchId);
- if (interval) {
- clearInterval(interval);
- io._arenaTimers.delete(matchId);
- console.log(`[1v1] Timer für Match ${matchId} gestoppt (beide bereit).`);
- }
-}
-
-function tryMatchmaking(io, newSocketId) {
- const challenger = waitingPool.get(newSocketId);
- if (!challenger) return;
-
- for (const [id, entry] of waitingPool) {
- if (id === newSocketId) continue;
-
- const levelDiff = Math.abs(entry.player.level - challenger.player.level);
- if (levelDiff <= LEVEL_RANGE) {
- waitingPool.delete(newSocketId);
- waitingPool.delete(id);
-
- const matchId = `match_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`;
-
- challenger.socket.emit("match_found", {
- matchId,
- opponent: entry.player,
- mySlot: "player1",
- });
-
- entry.socket.emit("match_found", {
- matchId,
- opponent: challenger.player,
- mySlot: "player2",
- });
-
- console.log(
- `[1v1] Match: ${challenger.player.name} (Lvl ${challenger.player.level})` +
- ` vs ${entry.player.name} (Lvl ${entry.player.level}) | ${matchId}`
- );
- return;
- }
- }
-}
-
-function registerArenaHandlers(io, socket) {
-
- /* ── Queue beitreten ── */
- socket.on("join_1v1", (playerData) => {
- if (waitingPool.has(socket.id)) return;
-
- const player = {
- id: playerData.id,
- name: playerData.name,
- level: Number(playerData.level) || 1,
- };
-
- waitingPool.set(socket.id, { socket, player });
-
- socket.emit("queue_status", {
- status: "waiting",
- poolSize: waitingPool.size,
- message: `Suche Gegner (Level ${player.level - LEVEL_RANGE}–${player.level + LEVEL_RANGE})…`,
- });
-
- console.log(`[1v1] ${player.name} (Lvl ${player.level}) im Pool. Größe: ${waitingPool.size}`);
- tryMatchmaking(io, socket.id);
- });
-
- /* ── Queue verlassen ── */
- socket.on("leave_1v1", () => {
- if (waitingPool.delete(socket.id)) {
- socket.emit("queue_status", { status: "left" });
- console.log(`[1v1] ${socket.id} hat Pool verlassen.`);
- }
- });
-
- /* ── Spielfeld: Spieler betritt Arena-Room ── */
- socket.on("arena_join", (data) => {
- const { matchId, slot } = data;
- if (!matchId || !slot) return;
-
- if (!io._arenaRooms) io._arenaRooms = new Map();
- if (!io._arenaRooms.has(matchId)) {
- io._arenaRooms.set(matchId, { sockets: {}, names: {} });
- }
-
- const room = io._arenaRooms.get(matchId);
- room.sockets[slot] = socket.id;
- room.names[slot] = socket.user || "Spieler";
-
- socket.join("arena_" + matchId);
-
- const otherSlot = slot === "player1" ? "player2" : "player1";
-
- if (room.sockets[otherSlot]) {
- io.to("arena_" + matchId).emit("arena_ready", {
- player1: room.names["player1"] || "Spieler 1",
- player2: room.names["player2"] || "Spieler 2",
- });
- console.log(`[Arena] Match ${matchId} bereit: ${room.names["player1"]} vs ${room.names["player2"]}`);
-
- // 30-Sekunden Bereit-Timer starten
- startReadyTimer(io, matchId);
- } else {
- socket.to("arena_" + matchId).emit("arena_opponent_joined", {
- name: room.names[slot],
- slot,
- });
- }
- });
-
- /* ── Bereit-System ── */
- socket.on("player_ready", (data) => {
- const { matchId, slot } = data;
- if (!matchId || !slot) return;
-
- if (!io._arenaReady) io._arenaReady = new Map();
- if (!io._arenaReady.has(matchId)) {
- io._arenaReady.set(matchId, new Set());
- }
-
- const readySet = io._arenaReady.get(matchId);
- readySet.add(slot);
-
- io.to("arena_" + matchId).emit("ready_status", {
- readyCount: readySet.size,
- readySlots: Array.from(readySet),
- });
-
- console.log(`[1v1] ${slot} bereit in ${matchId} (${readySet.size}/2)`);
-
- if (readySet.size >= 2) {
- stopReadyTimer(io, matchId);
- io._arenaReady.delete(matchId);
- }
- });
-
- /* ── Aufgeben ── */
- 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 });
- });
-
- /* ── Disconnect: aus Pool entfernen ── */
- socket.on("disconnect", () => {
- if (waitingPool.delete(socket.id)) {
- console.log(`[1v1] ${socket.id} disconnected – aus Pool entfernt.`);
- }
- });
-}
-
-module.exports = { registerArenaHandlers };
diff --git a/sockets/arena_socket.js b/sockets/arena.socket.js
similarity index 91%
rename from sockets/arena_socket.js
rename to sockets/arena.socket.js
index 889cda4..eea576b 100644
--- a/sockets/arena_socket.js
+++ b/sockets/arena.socket.js
@@ -367,7 +367,10 @@ function getRoom(io, matchId) {
// Sendet an beide Spieler über ihre gespeicherten Socket-IDs
function emitToMatch(io, matchId, event, data) {
const room = getRoom(io, matchId);
- if (!room) { console.warn(`[emitToMatch] Kein Room für ${matchId}`); return; }
+ if (!room) {
+ console.warn(`[emitToMatch] Kein Room für ${matchId}`);
+ return;
+ }
["player1", "player2"].forEach((slot) => {
if (room.sockets[slot]) {
io.to(room.sockets[slot]).emit(event, data);
@@ -442,7 +445,9 @@ function registerArenaHandlers(io, socket) {
}
// Sonst: bereits guter Name gespeichert → nicht überschreiben
- console.log(`[1v1] Name gesetzt: slot=${slot}, name=${room.names[slot]}, playerName=${playerName}`);
+ console.log(
+ `[1v1] Name gesetzt: slot=${slot}, name=${room.names[slot]}, playerName=${playerName}`,
+ );
socket.join("arena_" + matchId);
@@ -479,26 +484,23 @@ function registerArenaHandlers(io, socket) {
if (!io._arenaReady.has(matchId)) io._arenaReady.set(matchId, new Set());
const readySet = io._arenaReady.get(matchId);
readySet.add(slot);
- const readyData = { readyCount: readySet.size, readySlots: Array.from(readySet) };
+ const readyData = {
+ readyCount: readySet.size,
+ readySlots: Array.from(readySet),
+ };
emitToMatch(io, matchId, "ready_status", readyData);
- console.log(`[1v1] ready_status: ${readySet.size}/2 bereit | Match ${matchId}`);
+ console.log(
+ `[1v1] ready_status: ${readySet.size}/2 bereit | Match ${matchId}`,
+ );
if (readySet.size >= 2) {
stopReadyTimer(io, matchId);
io._arenaReady.delete(matchId);
-
- // Server startet Zug direkt – kein end_turn_init vom Client nötig
- // Startspieler: player1 (deterministisch, kein Flip nötig auf Server-Seite)
- const starterSlot = "player1";
- if (!io._turnInit) io._turnInit = new Set();
- if (!io._turnInit.has(matchId)) {
- io._turnInit.add(matchId);
- setTimeout(() => io._turnInit?.delete(matchId), 60000);
- const room = io._arenaRooms?.get(matchId);
- const boardCards = room?.boardCards || [];
- emitToMatch(io, matchId, "turn_change", { activeSlot: starterSlot, boardSync: boardCards });
- console.log(`[1v1] Spiel startet → ${starterSlot} beginnt | Match ${matchId}`);
- }
+ // Startspieler wird vom Client per start_turn_request gesendet
+ // (Client kennt durch seed-basiertes Flip wer links = wer anfängt)
+ console.log(
+ `[1v1] Beide bereit – warte auf start_turn_request | Match ${matchId}`,
+ );
}
});
@@ -522,7 +524,9 @@ function registerArenaHandlers(io, socket) {
boardSync: boardCards,
});
- console.log(`[1v1] Zug: ${slot} → ${nextSlot} | boardCards=${boardCards.length} | Match ${matchId}`);
+ console.log(
+ `[1v1] Zug: ${slot} → ${nextSlot} | boardCards=${boardCards.length} | Match ${matchId}`,
+ );
});
/* ── Karte gespielt → direkt an Gegner senden ── */
@@ -540,20 +544,29 @@ function registerArenaHandlers(io, socket) {
roomState.boardCards.push(data);
}
- console.log(`[1v1] card_played: ${data.card?.name} → ${data.boardSlot} | Match ${matchId}`);
+ console.log(
+ `[1v1] card_played: ${data.card?.name} → ${data.boardSlot} | Match ${matchId}`,
+ );
});
- socket.on("end_turn_init", (data) => {
+ // Client sendet nach ready_status den leftSlot (wer anfängt)
+ socket.on("start_turn_request", (data) => {
const { matchId, starterSlot } = data;
if (!matchId || !starterSlot) return;
- // Nur einmal senden (erster Empfang gewinnt)
if (!io._turnInit) io._turnInit = new Set();
+ // Nur einmal pro Match ausführen
if (io._turnInit.has(matchId)) return;
io._turnInit.add(matchId);
- emitToMatch(io, matchId, "turn_change", { activeSlot: starterSlot });
- console.log(`[1v1] Startzug: ${starterSlot} | Match ${matchId}`);
- // Cleanup nach 60s (lang genug damit doppelte end_turn_init geblockt bleiben)
setTimeout(() => io._turnInit?.delete(matchId), 60000);
+ const room = io._arenaRooms?.get(matchId);
+ const boardCards = room?.boardCards || [];
+ emitToMatch(io, matchId, "turn_change", {
+ activeSlot: starterSlot,
+ boardSync: boardCards,
+ });
+ console.log(
+ `[1v1] Spiel startet → ${starterSlot} (linker Spieler) beginnt | Match ${matchId}`,
+ );
});
/* ── 2v2 & 4v4 ── */
diff --git a/views/1v1-battlefield.ejs b/views/1v1-battlefield.ejs
index f4bca54..aa867eb 100644
--- a/views/1v1-battlefield.ejs
+++ b/views/1v1-battlefield.ejs
@@ -167,7 +167,8 @@
}
/* ── Reichweite & Laufen Badges (Hand + Board) ── */
- .cs-range, .cs-race {
+ .cs-range,
+ .cs-race {
position: absolute;
display: flex;
align-items: center;
@@ -182,14 +183,16 @@
pointer-events: none;
}
.cs-range {
- bottom: 22px; left: 3px;
- background: rgba(30,20,0,0.85);
+ bottom: 22px;
+ left: 3px;
+ background: rgba(30, 20, 0, 0.85);
border: 1px solid #e8b84b;
color: #e8b84b;
}
.cs-race {
- bottom: 6px; left: 3px;
- background: rgba(0,25,0,0.85);
+ bottom: 6px;
+ left: 3px;
+ background: rgba(0, 25, 0, 0.85);
border: 1px solid #7de87d;
color: #7de87d;
}
@@ -416,7 +419,7 @@
/* ── Stat-Icon SVGs ─────────────────────────────────── */
const SVG_RANGE = ``;
- const SVG_RACE = ``;
+ const SVG_RACE = ``;
/* ── Slot rendern ──────────────────────────────────── */
function renderHandSlot(id) {
@@ -437,8 +440,8 @@
const atkVal = card.attack ?? null;
const defVal = card.defends ?? null;
- const rngVal = card.range ?? null;
- const rceVal = card.race ?? null;
+ const rngVal = card.range ?? null;
+ const rceVal = card.race ?? null;
const statsHtml = `
@@ -450,12 +453,13 @@
: ""
}
${rngVal != null ? `${SVG_RANGE} ${rngVal}` : ""}
- ${rceVal != null ? `${SVG_RACE} ${rceVal}` : ""}
+ ${rceVal != null ? `${SVG_RACE} ${rceVal}` : ""}
`;
- const readyBadge = (isReady && isMyTurn)
- ? `SPIELEN
`
- : "";
+ const readyBadge =
+ isReady && isMyTurn
+ ? `SPIELEN
`
+ : "";
slot.innerHTML = card.image
? `
{
turnSecsLeft--;
- updateTimerUI(turnSecsLeft);
+ updateTimerUI(turnSecsLeft, activeName);
if (turnSecsLeft <= 0) {
clearInterval(turnTimerInt);
+ // Nur der aktive Spieler sendet end_turn
if (isMyTurn) {
tickHandCooldowns();
- drawNextCard(); // eine Karte aus dem Deck nachziehen
+ drawNextCard();
setTurnState(false);
socket.emit("end_turn", { matchId, slot: mySlot });
-
- // Watchdog: falls turn_change nach 5s nicht ankommt → selbst freischalten
- setTimeout(() => {
- if (!isMyTurn) {
- console.warn(
- "[1v1] Kein turn_change nach 5s – Zug lokal übergeben",
- );
- setTurnState(true);
- }
- }, 5000);
}
}
}, 1000);
@@ -593,15 +589,17 @@
if (wrap) wrap.style.display = "none";
}
- function updateTimerUI(secs) {
+ function updateTimerUI(secs, activeName) {
const num = document.getElementById("turn-timer-num");
const circle = document.getElementById("tt-circle");
+ const wrap = document.getElementById("turn-timer-wrap");
if (num) num.textContent = secs;
+ if (wrap && activeName) wrap.title = activeName + " ist am Zug";
if (circle) {
circle.style.strokeDashoffset = TT_CIRCUM * (1 - secs / TURN_SECONDS);
circle.style.stroke =
- secs > 10 ? "#27ae60" : secs > 5 ? "#f39c12" : "#e74c3c";
- if (secs <= 5) {
+ secs > 15 ? "#27ae60" : secs > 8 ? "#f39c12" : "#e74c3c";
+ if (secs <= 8) {
num.style.color = "#e74c3c";
num.style.animation = "tt-pulse 0.5s ease-in-out infinite";
} else {
@@ -619,7 +617,8 @@
let amILeftPlayer = null;
let isMyTurn = false;
- function setTurnState(myTurn) {
+ // activeName = wer gerade dran ist (für Timer-Text + Indicator)
+ function setTurnState(myTurn, activeName) {
isMyTurn = myTurn;
const btn = document.getElementById("end-turn-btn");
if (!btn) return;
@@ -628,36 +627,29 @@
btn.disabled = false;
btn.textContent = "Zug beenden";
btn.style.opacity = "1";
- // Draggable für bereite Karten aktivieren
- setTimeout(() => handCardIds.forEach(id => renderHandSlot(id)), 50);
document.getElementById("turn-indicator")?.remove();
- startTurnTimer();
- // Watchdog für wartenden Spieler abbrechen falls vorhanden
- if (window._waitWatchdog) {
- clearTimeout(window._waitWatchdog);
- window._waitWatchdog = null;
- }
+ // Draggable für bereite Karten aktivieren
+ setTimeout(() => handCardIds.forEach((id) => renderHandSlot(id)), 50);
+ // Timer für beide Spieler sichtbar starten
+ startTurnTimer(activeName || "Du");
} else {
btn.disabled = true;
btn.style.opacity = "0.4";
- stopTurnTimer();
- // Draggable deaktivieren
- handCardIds.forEach(id => { const s = document.getElementById(id); if(s){s.draggable=false;delete s.dataset.cardSlotId;} });
- showTurnIndicator();
- // Watchdog: falls nach 26s kein turn_change → eigenen Zug erzwingen
- if (window._waitWatchdog) clearTimeout(window._waitWatchdog);
- window._waitWatchdog = setTimeout(() => {
- if (!isMyTurn) {
- console.warn(
- "[1v1] Kein turn_change nach 26s – Zug lokal übernommen",
- );
- setTurnState(true);
+ // Draggable sofort deaktivieren
+ handCardIds.forEach((id) => {
+ const s = document.getElementById(id);
+ if (s) {
+ s.draggable = false;
+ delete s.dataset.cardSlotId;
}
- }, 26000);
+ });
+ showTurnIndicator(activeName);
+ // Timer für Gegner-Countdown auch anzeigen
+ startTurnTimer(activeName || "Gegner");
}
}
- function showTurnIndicator() {
+ function showTurnIndicator(activeName) {
document.getElementById("turn-indicator")?.remove();
const ind = document.createElement("div");
ind.id = "turn-indicator";
@@ -677,34 +669,35 @@
text-transform:uppercase;
box-shadow:0 4px 20px rgba(0,0,0,0.6);
`;
- ind.textContent = "⏳ Gegner ist am Zug";
+ ind.textContent = activeName
+ ? `⏳ ${activeName} ist am Zug`
+ : "⏳ Gegner ist am Zug";
document.body.appendChild(ind);
}
- /* ── Zug beenden: CD ticken + Karte ziehen + Server informieren ── */
+ /* ── Zug beenden: per Button oder Timer-Ablauf ── */
document.getElementById("end-turn-btn")?.addEventListener("click", () => {
if (!isMyTurn) return;
- // Sofort Timer stoppen und Zug abgeben – nicht auf turn_change warten
clearInterval(turnTimerInt);
stopTurnTimer();
tickHandCooldowns();
- // Eine Karte aus dem Deck nachziehen
drawNextCard();
setTurnState(false);
socket.emit("end_turn", { matchId, slot: mySlot });
+ // Server antwortet mit turn_change → dann wird setTurnState erneut aufgerufen
});
/* ── Hilfsfunktion: Karte mit Stats in einen Slot rendern ── */
function renderCardInSlot(slot, card) {
if (!slot || !card) return;
const atkVal = card.attack ?? null;
const defVal = card.defends ?? null;
- const cdVal = card.cooldown ?? null;
- const rngVal = card.range ?? null;
- const rceVal = card.race ?? null;
+ const cdVal = card.cooldown ?? null;
+ const rngVal = card.range ?? null;
+ const rceVal = card.race ?? null;
const hasAtk = atkVal != null;
const hasDef = defVal != null;
- const hasCd = cdVal != null;
+ const hasCd = cdVal != null;
const hasRng = rngVal != null;
const hasRce = rceVal != null;
const statsHtml =
@@ -713,9 +706,9 @@
${hasAtk ? `${atkVal}` : ""}
${hasDef ? `${defVal}` : ""}
- ${hasCd ? `${cdVal}` : ""}
+ ${hasCd ? `${cdVal}` : ""}
${hasRng ? `${SVG_RANGE} ${rngVal}` : ""}
- ${hasRce ? `${SVG_RACE} ${rceVal}` : ""}
+ ${hasRce ? `${SVG_RACE} ${rceVal}` : ""}
`
: "";
// note: SVG_RACE is defined as the walking icon above
@@ -741,15 +734,14 @@
const mySlot = urlParams.get("slot") || "<%= mySlot || 'player1' %>";
const amIPlayer1 = mySlot === "player1";
- // Board-Klasse setzen damit CSS die richtigen Zonen einfärbt
- document.querySelector(".board")?.classList.add(
- amIPlayer1 ? "my-side-left" : "my-side-right"
- );
+ // Board-Klasse wird erst nach ready_status gesetzt wenn amILeftPlayer bekannt ist
// Gegner-Name direkt aus URL setzen (kommt aus match_found, immer korrekt)
const opponentNameFromUrl = urlParams.get("opponent");
if (opponentNameFromUrl) {
- const oppEl = document.getElementById(amIPlayer1 ? "nameRight" : "nameLeft");
+ const oppEl = document.getElementById(
+ amIPlayer1 ? "nameRight" : "nameLeft",
+ );
if (oppEl) oppEl.textContent = decodeURIComponent(opponentNameFromUrl);
}
@@ -851,9 +843,17 @@
const currentOppName = oppEl?.textContent || "";
const urlOppName = urlParams.get("opponent");
// Nur überschreiben wenn Server einen echten Namen hat (nicht "Spieler"/"Spieler 1"/"Spieler 2")
- if (oppEl && oppName && !["Spieler", "Spieler 1", "Spieler 2", "Gegner"].includes(oppName)) {
+ if (
+ oppEl &&
+ oppName &&
+ !["Spieler", "Spieler 1", "Spieler 2", "Gegner"].includes(oppName)
+ ) {
oppEl.textContent = oppName;
- } else if (oppEl && urlOppName && (!currentOppName || currentOppName === "Gegner")) {
+ } else if (
+ oppEl &&
+ urlOppName &&
+ (!currentOppName || currentOppName === "Gegner")
+ ) {
oppEl.textContent = decodeURIComponent(urlOppName);
}
// Eigenen Namen sichern falls noch nicht gesetzt
@@ -884,18 +884,36 @@
/* ── Server: Zugwechsel ──────────────────────────────── */
socket.on("turn_change", (data) => {
- // Sicherheits-Fallback: Overlay entfernen falls noch vorhanden
document.getElementById("board-lock-overlay")?.remove();
document.getElementById("connecting-overlay")?.remove();
if (data.boardSync) applyBoardSync(data.boardSync);
- const nowMyTurn = data.activeSlot === mySlot;
- console.log("[1v1] turn_change:", data.activeSlot, "| ich:", mySlot, "| meinZug:", nowMyTurn);
- setTurnState(nowMyTurn);
+
+ // Aktiver Spieler: Slot aus Server-Daten
+ // Linker Spieler = window._leftSlot (gesetzt nach ready_status)
+ // Der linke Spieler fängt immer an.
+ const activeSlot = data.activeSlot;
+ const nowMyTurn = activeSlot === mySlot;
+
+ // Namen des aktiven Spielers für Timer + Indicator
+ const activeNameEl = document.getElementById(
+ activeSlot === "player1" ? "nameLeft" : "nameRight",
+ );
+ const activeName =
+ activeNameEl?.textContent || (nowMyTurn ? "Du" : "Gegner");
+
+ console.log(
+ `[1v1] turn_change: ${activeSlot} | ich: ${mySlot} | meinZug: ${nowMyTurn} | name: ${activeName}`,
+ );
+ setTurnState(nowMyTurn, activeName);
});
socket.on("turn_started", (data) => {
- setTurnState(data.slot === mySlot);
+ const myT = data.slot === mySlot;
+ const nameEl = document.getElementById(
+ data.slot === "player1" ? "nameLeft" : "nameRight",
+ );
+ setTurnState(myT, nameEl?.textContent || (myT ? "Du" : "Gegner"));
});
/* ── Bereit-System ──────────────────────────────────── */
@@ -994,9 +1012,38 @@
// Festlegen ob ich der linke Spieler bin
amILeftPlayer = flip ? mySlot === "player2" : mySlot === "player1";
- // Server startet Zug automatisch wenn beide bereit sind
- // turn_change kommt vom Server → kein end_turn_init nötig
- console.log("[1v1] Beide bereit – warte auf turn_change vom Server...");
+ // Board-CSS-Klasse jetzt setzen da amILeftPlayer bekannt ist
+ const board = document.querySelector(".board");
+ if (board) {
+ board.classList.remove("my-side-left", "my-side-right");
+ board.classList.add(
+ amILeftPlayer ? "my-side-left" : "my-side-right",
+ );
+ }
+
+ // Der LINKE Spieler fängt an → amILeftPlayer bestimmt wer turn_change bekommt
+ // Server schickt turn_change mit activeSlot=player1 → wir mappen das auf links
+ // Wenn ich der linke Spieler bin UND player1 bin → ich bin aktiv
+ // Wenn ich der linke Spieler bin aber player2 bin → ich bin auch aktiv (flip)
+ // → setTurnState direkt setzen basierend auf amILeftPlayer
+ const leftSlot = flip ? "player2" : "player1";
+ const iStartLeft = mySlot === leftSlot;
+ console.log(
+ `[1v1] Beide bereit | linker Spieler-Slot: ${leftSlot} | ich (${mySlot}) starte: ${iStartLeft}`,
+ );
+ window._leftSlot = leftSlot;
+
+ // Nur EINER der beiden Clients sendet start_turn_request
+ // Wir nehmen immer player1 als Absender (deterministisch, kein Doppel-Fire)
+ if (mySlot === "player1") {
+ socket.emit("start_turn_request", {
+ matchId,
+ starterSlot: leftSlot,
+ });
+ console.log(
+ `[1v1] start_turn_request gesendet: starterSlot=${leftSlot}`,
+ );
+ }
}
});
@@ -1219,7 +1266,6 @@
r.readAsDataURL(file);
}
-
/* ══════════════════════════════════════════════════════
DRAG & DROP – Karte aus Hand auf Board legen
══════════════════════════════════════════════════════ */
@@ -1230,7 +1276,10 @@
/* Welche Slot-Indizes darf ich bespielen? */
function isMyZone(slotIndex) {
const idx = Number(slotIndex);
- return amIPlayer1 ? idx <= 3 : idx >= 9;
+ // amILeftPlayer nach ready_status bekannt; Fallback: amIPlayer1
+ const iAmLeft = amILeftPlayer !== null ? amILeftPlayer : amIPlayer1;
+ // Linker Spieler: Slots 1–3 | Rechter Spieler: Slots 9–11
+ return iAmLeft ? idx <= 3 : idx >= 9;
}
/* Drop-Zones aktivieren / deaktivieren */
@@ -1250,16 +1299,16 @@
function renderCardOnBoard(slotEl, card) {
const atkVal = card.attack ?? null;
const defVal = card.defends ?? null;
- const cdVal = card.cooldown ?? null;
- const rngVal = card.range ?? null;
- const rceVal = card.race ?? null;
+ const cdVal = card.cooldown ?? null;
+ const rngVal = card.range ?? null;
+ const rceVal = card.race ?? null;
const statsHtml = `
${atkVal != null ? `${atkVal}` : ""}
${defVal != null ? `${defVal}` : ""}
- ${cdVal != null ? `${cdVal}` : ""}
+ ${cdVal != null ? `${cdVal}` : ""}
${rngVal != null ? `${SVG_RANGE} ${rngVal}` : ""}
- ${rceVal != null ? `${SVG_RACE} ${rceVal}` : ""}
+ ${rceVal != null ? `${SVG_RACE} ${rceVal}` : ""}
`;
slotEl.classList.add("slot-occupied");
slotEl.innerHTML = card.image
@@ -1280,7 +1329,10 @@
document.getElementById("handArea").addEventListener("dragstart", (e) => {
const slot = e.target.closest("[data-card-slot-id]");
- if (!slot || !isMyTurn) { e.preventDefault(); return; }
+ if (!slot || !isMyTurn) {
+ e.preventDefault();
+ return;
+ }
draggedCardSlotId = slot.dataset.cardSlotId;
slot.classList.add("dragging");
e.dataTransfer.effectAllowed = "move";
@@ -1308,7 +1360,9 @@
e.preventDefault();
e.dataTransfer.dropEffect = "move";
// Hover-Highlight
- row.querySelectorAll(".drop-zone-hover").forEach(s => s.classList.remove("drop-zone-hover"));
+ row
+ .querySelectorAll(".drop-zone-hover")
+ .forEach((s) => s.classList.remove("drop-zone-hover"));
slot.classList.add("drop-zone-hover");
});
@@ -1324,7 +1378,8 @@
const idx = Number(slot.dataset.slotIndex);
if (!isMyZone(idx) || boardState[slot.id]) return;
// Fallback: manche Browser liefern draggedCardSlotId über dataTransfer
- const sourceId = draggedCardSlotId || e.dataTransfer.getData("text/plain");
+ const sourceId =
+ draggedCardSlotId || e.dataTransfer.getData("text/plain");
if (!sourceId) return;
const cardState = handSlotState[sourceId];
@@ -1350,7 +1405,9 @@
card: cardState.card,
});
- console.log(`[1v1] Karte gespielt: ${cardState.card.name} → ${slot.id} (aus ${sourceId})`);
+ console.log(
+ `[1v1] Karte gespielt: ${cardState.card.name} → ${slot.id} (aus ${sourceId})`,
+ );
});
});
@@ -1364,18 +1421,22 @@
const slotEl = document.getElementById(data.boardSlot);
if (!slotEl) {
- console.warn("[1v1] card_played: Slot nicht gefunden:", data.boardSlot);
+ console.warn(
+ "[1v1] card_played: Slot nicht gefunden:",
+ data.boardSlot,
+ );
return;
}
boardState[data.boardSlot] = data.card;
renderCardOnBoard(slotEl, data.card);
- console.log("[1v1] Gegner Karte:", data.card?.name, "→", data.boardSlot);
+ console.log(
+ "[1v1] Gegner Karte:",
+ data.card?.name,
+ "→",
+ data.boardSlot,
+ );
});
- /* Wenn Zug wechselt: draggable aktualisieren */
- const _origSetTurnState = setTurnState;
- // setTurnState ist bereits definiert, wir patchen es nach
-
/* ── Event-Listener ─────────────────────────────────── */
document
.getElementById("bereit-btn")