dok/sockets/combat.js
2026-04-13 17:21:54 +01:00

183 lines
6.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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
// ...
});
*/