From ebb08e116065d150e85603cb198e8c2e337d7956 Mon Sep 17 00:00:00 2001 From: cay Date: Mon, 13 Apr 2026 17:30:53 +0100 Subject: [PATCH] gjnhtedr --- sockets/arena.socket.js | 2 +- sockets/combat.js | 182 +++++++++++++++------------------------- 2 files changed, 70 insertions(+), 114 deletions(-) diff --git a/sockets/arena.socket.js b/sockets/arena.socket.js index e3e8011..f1f1c73 100644 --- a/sockets/arena.socket.js +++ b/sockets/arena.socket.js @@ -596,7 +596,7 @@ function registerArenaHandlers(io, socket) { const nextSlot = slot === "player1" ? "player2" : "player1"; /* ── Kampfphase berechnen ── */ - const combatEvents = runCombatPhase(boardState, leftSlot); + const combatEvents = runCombatPhase(boardState, leftSlot, slot); // slot = aktiver Spieler // boardCards nach Kampf aktualisieren (Karten die gestorben sind fehlen jetzt) room.boardCards = boardStateToCards(boardState); diff --git a/sockets/combat.js b/sockets/combat.js index 1ecaf66..9bd2636 100644 --- a/sockets/combat.js +++ b/sockets/combat.js @@ -1,20 +1,10 @@ /** * sockets/combat.js – Server-seitige Kampfphasen-Logik für 1v1 * - * boardState-Format (server-seitig): - * { [slotId]: { card: { name, attack, defends, range, race, ... }, owner: 'player1'|'player2' } } + * ZWEI PHASEN PRO ZUG: * - * Bewegungsrichtung: - * leftSlot-Spieler → dir = +1 (Slot 1 → 11) - * rightSlot-Spieler → dir = −1 (Slot 11 → 1) - * - * Verarbeitungs-Reihenfolge: - * Slot 11 → 1, pro Slot: row1 zuerst, dann row2 - * - * Jede Karte wird genau einmal verarbeitet (Snapshot der Startreihenfolge). - * Bewegung: race Schritte vorwärts, stoppt vor eigener UND feindlicher Karte. - * Angriff: scannt range Felder vorwärts, überspringt eigene Karten, - * greift die erste feindliche Karte an (nur eine pro Zug). + * Phase 1 – BEWEGUNG (nur Karten des aktiven Spielers, vorderste zuerst) + * Phase 2 – ANGRIFF (nur Karten des aktiven Spielers, Slot 11→1) */ 'use strict'; @@ -22,64 +12,94 @@ /** * @param {Object} boardState – wird in-place verändert * @param {string} leftSlot – 'player1' oder 'player2' (wer links steht) + * @param {string} activeSlot – 'player1' oder 'player2' (wer gerade am Zug ist) * @returns {Array} – geordnete Event-Liste für den Client */ -function runCombatPhase(boardState, leftSlot) { +function runCombatPhase(boardState, leftSlot, activeSlot) { const events = []; - /* ── Reihenfolge einmalig snapshot-en ──────────────────────────── */ - const processingOrder = []; - for (let slotIndex = 11; slotIndex >= 1; slotIndex--) { + /* ════════════════════════════════════════════════════════ + PHASE 1: BEWEGUNG + Nur Karten des aktiven Spielers bewegen sich. + Vorderste Karte zuerst → Kette kann aufrücken. + Linker Spieler (dir +1): höchste Slot-Zahl zuerst + Rechter Spieler (dir -1): niedrigste Slot-Zahl zuerst + ════════════════════════════════════════════════════════ */ + + const myCards = []; + + for (let slotIndex = 1; slotIndex <= 11; slotIndex++) { for (const row of ['row1', 'row2']) { const slotId = `${row}-slot-${slotIndex}`; - if (boardState[slotId]) { - processingOrder.push(slotId); + if (boardState[slotId]?.owner === activeSlot) { + myCards.push(slotId); } } } - /* ── Jede Karte einzeln verarbeiten ────────────────────────────── */ - for (const startSlotId of processingOrder) { - const entry = boardState[startSlotId]; - if (!entry) continue; // wurde in dieser Runde bereits getötet + const isActiveLeft = activeSlot === leftSlot; + const dir = isActiveLeft ? 1 : -1; - const { card, owner } = entry; - const isLeft = owner === leftSlot; - const dir = isLeft ? 1 : -1; - const row = startSlotId.split('-slot-')[0]; // 'row1' oder 'row2' + // Vorderste Karte zuerst + myCards.sort((a, b) => { + const ia = parseInt(a.split('-slot-')[1], 10); + const ib = parseInt(b.split('-slot-')[1], 10); + return isActiveLeft ? ib - ia : ia - ib; // links: 11→1 | rechts: 1→11 + }); + + for (const startSlotId of myCards) { + const entry = boardState[startSlotId]; + if (!entry) continue; + + const { card } = entry; + const row = startSlotId.split('-slot-')[0]; + const race = card.race ?? 0; let currentPos = parseInt(startSlotId.split('-slot-')[1], 10); let currentSlotId = startSlotId; - /* ── BEWEGUNG (race) ──────────────────────────────────────────── */ - const race = card.race ?? 0; - for (let step = 0; step < race; step++) { const nextPos = currentPos + dir; if (nextPos < 1 || nextPos > 11) break; const nextSlotId = `${row}-slot-${nextPos}`; + if (boardState[nextSlotId]) break; // eigene oder feindliche Karte blockiert - // Blockiert durch eigene ODER feindliche Karte → stehen bleiben - if (boardState[nextSlotId]) break; - - // Slot frei → Karte verschieben delete boardState[currentSlotId]; boardState[nextSlotId] = entry; - events.push({ - type : 'move', - from : currentSlotId, - to : nextSlotId, - owner, - }); + events.push({ type: 'move', from: currentSlotId, to: nextSlotId, owner: activeSlot }); currentSlotId = nextSlotId; currentPos = nextPos; } + } - /* ── ANGRIFF (range) ──────────────────────────────────────────── */ - const range = card.range ?? 0; + /* ════════════════════════════════════════════════════════ + PHASE 2: ANGRIFF + Nur Karten des aktiven Spielers greifen an. + Feste Reihenfolge: Slot 11 → 1, row1 vor row2. + Snapshot nach den Bewegungen. + ════════════════════════════════════════════════════════ */ + + const attackOrder = []; + for (let slotIndex = 11; slotIndex >= 1; slotIndex--) { + for (const row of ['row1', 'row2']) { + const slotId = `${row}-slot-${slotIndex}`; + if (boardState[slotId]?.owner === activeSlot) { + attackOrder.push(slotId); + } + } + } + + for (const slotId of attackOrder) { + const entry = boardState[slotId]; + if (!entry) continue; + + const { card } = entry; + const row = slotId.split('-slot-')[0]; + const currentPos = parseInt(slotId.split('-slot-')[1], 10); + const range = card.range ?? 0; for (let r = 1; r <= range; r++) { const targetPos = currentPos + dir * r; @@ -88,37 +108,27 @@ function runCombatPhase(boardState, leftSlot) { const targetSlotId = `${row}-slot-${targetPos}`; const target = boardState[targetSlotId]; - // Leeres Feld → weiter scannen - if (!target) continue; + if (!target) continue; // leeres Feld → weiter scannen + if (target.owner === activeSlot) continue; // eigene Karte → Range geht hindurch - // Eigene Karte → Range geht hindurch (keine Aktion, weiter scannen) - if (target.owner === owner) continue; - - /* ── Feindliche Karte gefunden → Angriff ─────────────────── */ + // Feindliche Karte → Angriff const atk = card.attack ?? 0; - target.card = { - ...target.card, - defends: (target.card.defends ?? 0) - atk, - }; + target.card = { ...target.card, defends: (target.card.defends ?? 0) - atk }; events.push({ type : 'attack', - from : currentSlotId, + from : slotId, to : targetSlotId, damage : atk, remainingDef: target.card.defends, }); - // Karte sterben lassen wenn defends ≤ 0 if (target.card.defends <= 0) { delete boardState[targetSlotId]; - events.push({ - type : 'die', - slotId: targetSlotId, - }); + events.push({ type: 'die', slotId: targetSlotId }); } - break; // Nur die erste feindliche Karte pro Runde angreifen + break; // nur erste feindliche Karte angreifen } } @@ -126,57 +136,3 @@ function runCombatPhase(boardState, leftSlot) { } module.exports = { runCombatPhase }; - - -/* ═══════════════════════════════════════════════════════════════════ - INTEGRATION IN DEN BESTEHENDEN SOCKET-SERVER - (nur als Referenz-Snippet – in die eigentliche arena-socket.js einbauen) -═══════════════════════════════════════════════════════════════════ */ - -/* - -const { runCombatPhase } = require('./combat'); - -// Pro Match einen boardState auf dem Server halten: -// matchBoards[matchId] = { [slotId]: { card, owner } } -const matchBoards = {}; -const matchLeftSlot = {}; // matchLeftSlot[matchId] = 'player1' | 'player2' - -// Wenn eine Karte gespielt wird → server-seitigen boardState aktualisieren: -socket.on('card_played', data => { - const { matchId, slot, boardSlot, card } = data; - if (!matchBoards[matchId]) matchBoards[matchId] = {}; - matchBoards[matchId][boardSlot] = { card, owner: slot }; - // ...weiter wie bisher (an Gegner broadcasten, boardSync etc.) -}); - -// leftSlot merken sobald er feststeht (z.B. in ready_status oder start_turn_request): -socket.on('start_turn_request', data => { - matchLeftSlot[data.matchId] = data.starterSlot; - // ...Zug starten wie bisher -}); - -// Nach end_turn: Kampfphase starten, dann Zug wechseln: -socket.on('end_turn', data => { - const { matchId, slot } = data; - const board = matchBoards[matchId] ?? {}; - const leftSlot = matchLeftSlot[matchId] ?? 'player1'; - - // Kampfphase berechnen - const events = runCombatPhase(board, leftSlot); - - // finalBoard als flaches Array für den boardSync senden - const finalBoard = Object.entries(board).map(([boardSlot, entry]) => ({ - boardSlot, - card : entry.card, - owner: entry.owner, - })); - - // An BEIDE Spieler senden (Reihenfolge & Ergebnis ist identisch) - io.to(matchId).emit('combat_phase', { events, finalBoard }); - - // Cooldowns / Zug wechseln wie bisher - // ... -}); - -*/