/** * sockets/combat.js – Server-seitige Kampfphasen-Logik für 1v1 * * ZWEI PHASEN 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'; /** * @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, activeSlot) { const events = []; /* ════════════════════════════════════════════════════════ 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]?.owner === activeSlot) { myCards.push(slotId); } } } const isActiveLeft = activeSlot === leftSlot; const dir = isActiveLeft ? 1 : -1; // 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; 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 delete boardState[currentSlotId]; boardState[nextSlotId] = entry; events.push({ type: 'move', from: currentSlotId, to: nextSlotId, owner: activeSlot }); currentSlotId = nextSlotId; currentPos = nextPos; } } /* ════════════════════════════════════════════════════════ 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; if (targetPos < 1 || targetPos > 11) break; const targetSlotId = `${row}-slot-${targetPos}`; const target = boardState[targetSlotId]; if (!target) continue; // leeres Feld → weiter scannen if (target.owner === activeSlot) continue; // eigene Karte → Range geht hindurch // Feindliche Karte → Angriff const atk = card.attack ?? 0; target.card = { ...target.card, defends: (target.card.defends ?? 0) - atk }; events.push({ type : 'attack', from : slotId, to : targetSlotId, damage : atk, remainingDef: target.card.defends, }); if (target.card.defends <= 0) { delete boardState[targetSlotId]; events.push({ type: 'die', slotId: targetSlotId }); } break; // nur erste feindliche Karte angreifen } } return events; } module.exports = { runCombatPhase };