/** * sockets/combat.js – Server-seitige Kampfphasen-Logik für 1v1 * * boardState-Format (server-seitig): * { [slotId]: { card: { name, attack, defends, range, race, ... }, owner: 'player1'|'player2' } } * * 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). */ 'use strict'; /** * @param {Object} boardState – wird in-place verändert * @param {string} leftSlot – 'player1' oder 'player2' (wer links steht) * @returns {Array} – geordnete Event-Liste für den Client */ function runCombatPhase(boardState, leftSlot) { const events = []; /* ── Reihenfolge einmalig snapshot-en ──────────────────────────── */ const processingOrder = []; for (let slotIndex = 11; slotIndex >= 1; slotIndex--) { for (const row of ['row1', 'row2']) { const slotId = `${row}-slot-${slotIndex}`; if (boardState[slotId]) { processingOrder.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 { card, owner } = entry; const isLeft = owner === leftSlot; const dir = isLeft ? 1 : -1; const row = startSlotId.split('-slot-')[0]; // 'row1' oder 'row2' 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}`; // 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, }); currentSlotId = nextSlotId; currentPos = nextPos; } /* ── ANGRIFF (range) ──────────────────────────────────────────── */ const range = card.range ?? 0; for (let r = 1; r <= range; r++) { const targetPos = currentPos + dir * r; if (targetPos < 1 || targetPos > 11) break; const targetSlotId = `${row}-slot-${targetPos}`; const target = boardState[targetSlotId]; // Leeres Feld → weiter scannen if (!target) continue; // Eigene Karte → Range geht hindurch (keine Aktion, weiter scannen) if (target.owner === owner) continue; /* ── Feindliche Karte gefunden → Angriff ─────────────────── */ const atk = card.attack ?? 0; target.card = { ...target.card, defends: (target.card.defends ?? 0) - atk, }; events.push({ type : 'attack', from : currentSlotId, 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, }); } break; // Nur die erste feindliche Karte pro Runde angreifen } } return events; } 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 // ... }); */