This commit is contained in:
cay 2026-04-13 17:30:53 +01:00
parent a85ac02f25
commit ebb08e1160
2 changed files with 70 additions and 114 deletions

View File

@ -596,7 +596,7 @@ function registerArenaHandlers(io, socket) {
const nextSlot = slot === "player1" ? "player2" : "player1"; const nextSlot = slot === "player1" ? "player2" : "player1";
/* ── Kampfphase berechnen ── */ /* ── 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) // boardCards nach Kampf aktualisieren (Karten die gestorben sind fehlen jetzt)
room.boardCards = boardStateToCards(boardState); room.boardCards = boardStateToCards(boardState);

View File

@ -1,20 +1,10 @@
/** /**
* sockets/combat.js Server-seitige Kampfphasen-Logik für 1v1 * sockets/combat.js Server-seitige Kampfphasen-Logik für 1v1
* *
* boardState-Format (server-seitig): * ZWEI PHASEN PRO ZUG:
* { [slotId]: { card: { name, attack, defends, range, race, ... }, owner: 'player1'|'player2' } }
* *
* Bewegungsrichtung: * Phase 1 BEWEGUNG (nur Karten des aktiven Spielers, vorderste zuerst)
* leftSlot-Spieler dir = +1 (Slot 1 11) * Phase 2 ANGRIFF (nur Karten des aktiven Spielers, Slot 111)
* 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'; 'use strict';
@ -22,64 +12,94 @@
/** /**
* @param {Object} boardState wird in-place verändert * @param {Object} boardState wird in-place verändert
* @param {string} leftSlot 'player1' oder 'player2' (wer links steht) * @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 * @returns {Array} geordnete Event-Liste für den Client
*/ */
function runCombatPhase(boardState, leftSlot) { function runCombatPhase(boardState, leftSlot, activeSlot) {
const events = []; const events = [];
/* ── Reihenfolge einmalig snapshot-en ──────────────────────────── */ /*
const processingOrder = []; PHASE 1: BEWEGUNG
for (let slotIndex = 11; slotIndex >= 1; slotIndex--) { 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']) { for (const row of ['row1', 'row2']) {
const slotId = `${row}-slot-${slotIndex}`; const slotId = `${row}-slot-${slotIndex}`;
if (boardState[slotId]) { if (boardState[slotId]?.owner === activeSlot) {
processingOrder.push(slotId); myCards.push(slotId);
} }
} }
} }
/* ── Jede Karte einzeln verarbeiten ────────────────────────────── */ const isActiveLeft = activeSlot === leftSlot;
for (const startSlotId of processingOrder) { const dir = isActiveLeft ? 1 : -1;
const entry = boardState[startSlotId];
if (!entry) continue; // wurde in dieser Runde bereits getötet
const { card, owner } = entry; // Vorderste Karte zuerst
const isLeft = owner === leftSlot; myCards.sort((a, b) => {
const dir = isLeft ? 1 : -1; const ia = parseInt(a.split('-slot-')[1], 10);
const row = startSlotId.split('-slot-')[0]; // 'row1' oder 'row2' 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 currentPos = parseInt(startSlotId.split('-slot-')[1], 10);
let currentSlotId = startSlotId; let currentSlotId = startSlotId;
/* ── BEWEGUNG (race) ──────────────────────────────────────────── */
const race = card.race ?? 0;
for (let step = 0; step < race; step++) { for (let step = 0; step < race; step++) {
const nextPos = currentPos + dir; const nextPos = currentPos + dir;
if (nextPos < 1 || nextPos > 11) break; if (nextPos < 1 || nextPos > 11) break;
const nextSlotId = `${row}-slot-${nextPos}`; 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]; delete boardState[currentSlotId];
boardState[nextSlotId] = entry; boardState[nextSlotId] = entry;
events.push({ events.push({ type: 'move', from: currentSlotId, to: nextSlotId, owner: activeSlot });
type : 'move',
from : currentSlotId,
to : nextSlotId,
owner,
});
currentSlotId = nextSlotId; currentSlotId = nextSlotId;
currentPos = nextPos; 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++) { for (let r = 1; r <= range; r++) {
const targetPos = currentPos + dir * r; const targetPos = currentPos + dir * r;
@ -88,37 +108,27 @@ function runCombatPhase(boardState, leftSlot) {
const targetSlotId = `${row}-slot-${targetPos}`; const targetSlotId = `${row}-slot-${targetPos}`;
const target = boardState[targetSlotId]; const target = boardState[targetSlotId];
// Leeres Feld → weiter scannen if (!target) continue; // leeres Feld → weiter scannen
if (!target) continue; if (target.owner === activeSlot) continue; // eigene Karte → Range geht hindurch
// Eigene Karte → Range geht hindurch (keine Aktion, weiter scannen) // Feindliche Karte → Angriff
if (target.owner === owner) continue;
/* ── Feindliche Karte gefunden → Angriff ─────────────────── */
const atk = card.attack ?? 0; const atk = card.attack ?? 0;
target.card = { target.card = { ...target.card, defends: (target.card.defends ?? 0) - atk };
...target.card,
defends: (target.card.defends ?? 0) - atk,
};
events.push({ events.push({
type : 'attack', type : 'attack',
from : currentSlotId, from : slotId,
to : targetSlotId, to : targetSlotId,
damage : atk, damage : atk,
remainingDef: target.card.defends, remainingDef: target.card.defends,
}); });
// Karte sterben lassen wenn defends ≤ 0
if (target.card.defends <= 0) { if (target.card.defends <= 0) {
delete boardState[targetSlotId]; delete boardState[targetSlotId];
events.push({ events.push({ type: 'die', slotId: targetSlotId });
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 }; 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
// ...
});
*/