fhj,kzfu
This commit is contained in:
parent
3b173ec9de
commit
0fd5ad2ca6
@ -1154,3 +1154,45 @@ body {
|
||||
border: 1px solid #7de87d;
|
||||
color: #7de87d;
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════
|
||||
SPIELER-FARBEN: Blau (links) & Rot (rechts)
|
||||
═══════════════════════════════════════════════════════════ */
|
||||
|
||||
/* ── Karten auf dem Spielfeld ── */
|
||||
.card-slot.slot-owner-left {
|
||||
border-color: rgba(60, 140, 255, 0.9) !important;
|
||||
box-shadow:
|
||||
0 0 calc(var(--s) * 14) rgba(60, 140, 255, 0.45),
|
||||
inset 0 0 calc(var(--s) * 10) rgba(60, 140, 255, 0.1);
|
||||
}
|
||||
.card-slot.slot-owner-right {
|
||||
border-color: rgba(220, 60, 60, 0.9) !important;
|
||||
box-shadow:
|
||||
0 0 calc(var(--s) * 14) rgba(220, 60, 60, 0.45),
|
||||
inset 0 0 calc(var(--s) * 10) rgba(220, 60, 60, 0.1);
|
||||
}
|
||||
|
||||
/* ── Avatare ── */
|
||||
.avatar.av-team-left {
|
||||
border-color: rgba(60, 140, 255, 0.95) !important;
|
||||
box-shadow:
|
||||
0 0 calc(var(--s) * 28) rgba(60, 140, 255, 0.5),
|
||||
inset 0 0 calc(var(--s) * 20) rgba(0, 0, 0, 0.5) !important;
|
||||
}
|
||||
.avatar.av-team-left:hover {
|
||||
box-shadow:
|
||||
0 0 calc(var(--s) * 42) rgba(60, 140, 255, 0.75),
|
||||
inset 0 0 calc(var(--s) * 20) rgba(0, 0, 0, 0.5) !important;
|
||||
}
|
||||
.avatar.av-team-right {
|
||||
border-color: rgba(220, 60, 60, 0.95) !important;
|
||||
box-shadow:
|
||||
0 0 calc(var(--s) * 28) rgba(220, 60, 60, 0.5),
|
||||
inset 0 0 calc(var(--s) * 20) rgba(0, 0, 0, 0.5) !important;
|
||||
}
|
||||
.avatar.av-team-right:hover {
|
||||
box-shadow:
|
||||
0 0 calc(var(--s) * 42) rgba(220, 60, 60, 0.75),
|
||||
inset 0 0 calc(var(--s) * 20) rgba(0, 0, 0, 0.5) !important;
|
||||
}
|
||||
|
||||
@ -289,6 +289,19 @@ document.getElementById('end-turn-btn')?.addEventListener('click', endMyTurn);
|
||||
*/
|
||||
const boardState = {};
|
||||
|
||||
/* ── Spieler-Farbe auf Slot oder Avatar anwenden ────────────────── */
|
||||
function applyOwnerStyle(el, owner) {
|
||||
if (!el || !owner) return;
|
||||
const isLeft = owner === (window._leftSlot || 'player1');
|
||||
if (isLeft) {
|
||||
el.style.borderColor = 'rgba(60, 140, 255, 0.95)';
|
||||
el.style.boxShadow = '0 0 14px rgba(60, 140, 255, 0.5), inset 0 0 10px rgba(60,140,255,0.08)';
|
||||
} else {
|
||||
el.style.borderColor = 'rgba(220, 60, 60, 0.95)';
|
||||
el.style.boxShadow = '0 0 14px rgba(220, 60, 60, 0.5), inset 0 0 10px rgba(220,60,60,0.08)';
|
||||
}
|
||||
}
|
||||
|
||||
function buildStatsHtml(card) {
|
||||
const atk = card.attack ?? null;
|
||||
const def = card.defends ?? null;
|
||||
@ -304,9 +317,10 @@ function buildStatsHtml(card) {
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function renderCardOnBoard(slotEl, card) {
|
||||
function renderCardOnBoard(slotEl, card, owner) {
|
||||
const statsHtml = buildStatsHtml(card);
|
||||
slotEl.classList.add('slot-occupied');
|
||||
applyOwnerStyle(slotEl, owner);
|
||||
slotEl.innerHTML = card.image
|
||||
? `<img src="/images/cards/${card.image}" onerror="this.src='/images/items/rueckseite.png'" title="${card.name}" style="width:100%;height:100%;object-fit:cover;border-radius:7px;display:block;">${statsHtml}`
|
||||
: `<div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;gap:4px;font-family:Cinzel,serif;padding:4px;"><span style="font-size:18px;">\u2694\uFE0F</span><span style="font-size:9px;color:#f0d9a6;text-align:center;">${card.name}</span></div>${statsHtml}`;
|
||||
@ -326,6 +340,8 @@ function clearBoardSlot(slotId) {
|
||||
const el = document.getElementById(slotId);
|
||||
if (!el) return;
|
||||
el.classList.remove('slot-occupied');
|
||||
el.style.borderColor = '';
|
||||
el.style.boxShadow = '';
|
||||
el.innerHTML = '<span class="slot-icon">\u2736</span><span class="slot-num">' + slotId.split('-slot-')[1] + '</span>';
|
||||
}
|
||||
|
||||
@ -382,7 +398,7 @@ function applyBoardSync(cards) {
|
||||
if (!slotEl || boardState[cd.boardSlot]) return;
|
||||
const owner = cd.owner ?? (cd.slot ?? 'unknown');
|
||||
boardState[cd.boardSlot] = { card: cd.card, owner };
|
||||
renderCardOnBoard(slotEl, cd.card);
|
||||
renderCardOnBoard(slotEl, cd.card, cd.owner);
|
||||
});
|
||||
console.log('[1v1] Board sync:', cards.length, 'Karten');
|
||||
}
|
||||
@ -500,6 +516,18 @@ socket.on('ready_status', data => {
|
||||
document.getElementById('board-lock-overlay')?.remove();
|
||||
amILeftPlayer = flip ? mySlot === 'player2' : mySlot === 'player1';
|
||||
|
||||
// Avatare farbig umranden: links=blau, rechts=rot
|
||||
const _avL = document.getElementById('avLeft');
|
||||
const _avR = document.getElementById('avRight');
|
||||
if (_avL) {
|
||||
_avL.style.borderColor = 'rgba(60, 140, 255, 0.95)';
|
||||
_avL.style.boxShadow = '0 0 28px rgba(60, 140, 255, 0.55), inset 0 0 20px rgba(0,0,0,0.5)';
|
||||
}
|
||||
if (_avR) {
|
||||
_avR.style.borderColor = 'rgba(220, 60, 60, 0.95)';
|
||||
_avR.style.boxShadow = '0 0 28px rgba(220, 60, 60, 0.55), inset 0 0 20px rgba(0,0,0,0.5)';
|
||||
}
|
||||
|
||||
const board = document.querySelector('.board');
|
||||
if (board) {
|
||||
board.classList.remove('my-side-left', 'my-side-right');
|
||||
@ -509,6 +537,13 @@ socket.on('ready_status', data => {
|
||||
const leftSlot = flip ? 'player2' : 'player1';
|
||||
window._leftSlot = leftSlot;
|
||||
console.log(`[1v1] linker Slot: ${leftSlot} | ich: ${mySlot}`);
|
||||
|
||||
// Bereits auf dem Board liegende Karten jetzt korrekt einfärben
|
||||
// (boardSync kann vor _leftSlot ankommen)
|
||||
Object.entries(boardState).forEach(([slotId, entry]) => {
|
||||
const el = document.getElementById(slotId);
|
||||
if (el && entry?.owner) applyOwnerStyle(el, entry.owner);
|
||||
});
|
||||
if (mySlot === 'player1') {
|
||||
socket.emit('start_turn_request', { matchId, starterSlot: leftSlot });
|
||||
}
|
||||
@ -588,7 +623,7 @@ document.getElementById('handArea').addEventListener('dragend', e => {
|
||||
|
||||
/* ── boardState mit owner speichern ─────────────────── */
|
||||
boardState[slot.id] = { card: cardState.card, owner: mySlot };
|
||||
renderCardOnBoard(slot, cardState.card);
|
||||
renderCardOnBoard(slot, cardState.card, mySlot);
|
||||
slot.classList.remove('drop-zone-active', 'drop-zone-hover');
|
||||
|
||||
socket.emit('card_played', {
|
||||
@ -607,7 +642,7 @@ socket.on('card_played', data => {
|
||||
|
||||
/* ── boardState mit owner des Gegners speichern ─────── */
|
||||
boardState[data.boardSlot] = { card: data.card, owner: data.slot };
|
||||
renderCardOnBoard(slotEl, data.card);
|
||||
renderCardOnBoard(slotEl, data.card, data.slot);
|
||||
console.log('[1v1] Gegner Karte:', data.card?.name, '->', data.boardSlot);
|
||||
});
|
||||
|
||||
@ -667,7 +702,7 @@ function applyMoveEvent(ev) {
|
||||
fromEl.classList.remove('slot-occupied');
|
||||
fromEl.innerHTML = '<span class="slot-icon">\u2736</span><span class="slot-num">' + ev.from.split('-slot-')[1] + '</span>';
|
||||
|
||||
renderCardOnBoard(toEl, entry.card);
|
||||
renderCardOnBoard(toEl, entry.card, entry.owner);
|
||||
|
||||
console.log(`[Combat] Bewegt: ${entry.card.name} ${ev.from} → ${ev.to}`);
|
||||
}
|
||||
@ -722,7 +757,7 @@ function applyFinalBoard(finalBoard) {
|
||||
const slotEl = document.getElementById(entry.boardSlot);
|
||||
if (!slotEl) return;
|
||||
boardState[entry.boardSlot] = { card: entry.card, owner: entry.owner };
|
||||
renderCardOnBoard(slotEl, entry.card);
|
||||
renderCardOnBoard(slotEl, entry.card, entry.owner);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -185,7 +185,7 @@ router.get("/decks/:id/cards", async (req, res) => {
|
||||
const [cards] = await db.query(
|
||||
`SELECT
|
||||
dc.card_id,
|
||||
dc.amount,
|
||||
LEAST(dc.amount, COALESCE(uc.amount, 0)) AS amount,
|
||||
c.name,
|
||||
c.image,
|
||||
c.rarity,
|
||||
@ -195,10 +195,13 @@ router.get("/decks/:id/cards", async (req, res) => {
|
||||
c.\`range\`,
|
||||
c.\`race\`
|
||||
FROM deck_cards dc
|
||||
JOIN cards c ON c.id = dc.card_id
|
||||
JOIN cards c ON c.id = dc.card_id
|
||||
LEFT JOIN user_cards uc ON uc.card_id = dc.card_id
|
||||
AND uc.user_id = ?
|
||||
WHERE dc.deck_id = ?
|
||||
AND COALESCE(uc.amount, 0) > 0
|
||||
ORDER BY c.name`,
|
||||
[deckId]
|
||||
[userId, deckId]
|
||||
);
|
||||
res.json(cards);
|
||||
} catch (err) {
|
||||
|
||||
@ -86,7 +86,9 @@ router.post("/cards/combine", requireLogin, async (req, res) => {
|
||||
}
|
||||
|
||||
/* ── 6. Karten immer abziehen (Glücksspiel!) ── */
|
||||
if (owned.amount - amount <= 0) {
|
||||
const remainingAfter = owned.amount - amount;
|
||||
|
||||
if (remainingAfter <= 0) {
|
||||
await db.query(
|
||||
"DELETE FROM user_cards WHERE user_id = ? AND card_id = ?",
|
||||
[userId, card_id]
|
||||
@ -98,6 +100,30 @@ router.post("/cards/combine", requireLogin, async (req, res) => {
|
||||
);
|
||||
}
|
||||
|
||||
/* ── 6b. Decks synchronisieren ──────────────────────────────────
|
||||
Karten die durch Kombination verbraucht wurden, müssen auch aus
|
||||
allen Decks des Spielers entfernt / reduziert werden.
|
||||
Sonst steht im Deck mehr als der Spieler tatsächlich besitzt.
|
||||
────────────────────────────────────────────────────────────────── */
|
||||
if (remainingAfter <= 0) {
|
||||
// Keine Exemplare mehr vorhanden → aus allen Decks löschen
|
||||
await db.query(
|
||||
`DELETE dc FROM deck_cards dc
|
||||
JOIN decks d ON d.id = dc.deck_id
|
||||
WHERE d.user_id = ? AND dc.card_id = ?`,
|
||||
[userId, card_id]
|
||||
);
|
||||
} else {
|
||||
// Noch welche vorhanden → Deck-Menge auf verbleibende Menge deckeln
|
||||
await db.query(
|
||||
`UPDATE deck_cards dc
|
||||
JOIN decks d ON d.id = dc.deck_id
|
||||
SET dc.amount = LEAST(dc.amount, ?)
|
||||
WHERE d.user_id = ? AND dc.card_id = ?`,
|
||||
[remainingAfter, userId, card_id]
|
||||
);
|
||||
}
|
||||
|
||||
/* ── 7. Zufallsroll gegen Chance ── */
|
||||
const roll = Math.random() * 100;
|
||||
const success = roll <= chance;
|
||||
|
||||
@ -1,33 +1,29 @@
|
||||
/**
|
||||
* sockets/combat.js – Server-seitige Kampfphasen-Logik für 1v1
|
||||
*
|
||||
* ZWEI PHASEN PRO ZUG:
|
||||
* PRO KARTE: erst bewegen, dann sofort angreifen → dann nächste Karte.
|
||||
*
|
||||
* Phase 1 – BEWEGUNG (nur Karten des aktiven Spielers, vorderste zuerst)
|
||||
* Phase 2 – ANGRIFF (nur Karten des aktiven Spielers, Slot 11→1)
|
||||
* Reihenfolge (nur Karten des aktiven Spielers):
|
||||
* Linker Spieler (dir +1): höchste Slot-Zahl zuerst (11→1)
|
||||
* Rechter Spieler (dir -1): niedrigste Slot-Zahl zuerst (1→11)
|
||||
* → Vorderste Karte verarbeitet zuerst, macht Platz für die dahinter.
|
||||
*/
|
||||
|
||||
'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)
|
||||
* @param {string} leftSlot – wer links steht ('player1'|'player2')
|
||||
* @param {string} activeSlot – wer gerade am Zug ist ('player1'|'player2')
|
||||
* @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 events = [];
|
||||
const isActiveLeft = activeSlot === leftSlot;
|
||||
const dir = isActiveLeft ? 1 : -1;
|
||||
|
||||
/* ── Karten des aktiven Spielers sammeln ────────────────── */
|
||||
const myCards = [];
|
||||
|
||||
for (let slotIndex = 1; slotIndex <= 11; slotIndex++) {
|
||||
for (const row of ['row1', 'row2']) {
|
||||
const slotId = `${row}-slot-${slotIndex}`;
|
||||
@ -37,19 +33,17 @@ function runCombatPhase(boardState, leftSlot, activeSlot) {
|
||||
}
|
||||
}
|
||||
|
||||
const isActiveLeft = activeSlot === leftSlot;
|
||||
const dir = isActiveLeft ? 1 : -1;
|
||||
|
||||
// Vorderste Karte zuerst
|
||||
/* ── 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
|
||||
return isActiveLeft ? ib - ia : ia - ib;
|
||||
});
|
||||
|
||||
/* ── Jede Karte: erst bewegen, dann sofort angreifen ────── */
|
||||
for (const startSlotId of myCards) {
|
||||
const entry = boardState[startSlotId];
|
||||
if (!entry) continue;
|
||||
if (!entry) continue; // wurde durch vorherigen Angriff bereits entfernt (sollte nicht vorkommen, aber sicher ist sicher)
|
||||
|
||||
const { card } = entry;
|
||||
const row = startSlotId.split('-slot-')[0];
|
||||
@ -58,6 +52,7 @@ function runCombatPhase(boardState, leftSlot, activeSlot) {
|
||||
let currentPos = parseInt(startSlotId.split('-slot-')[1], 10);
|
||||
let currentSlotId = startSlotId;
|
||||
|
||||
/* ── 1. BEWEGEN ─────────────────────────────────────── */
|
||||
for (let step = 0; step < race; step++) {
|
||||
const nextPos = currentPos + dir;
|
||||
if (nextPos < 1 || nextPos > 11) break;
|
||||
@ -73,51 +68,27 @@ function runCombatPhase(boardState, leftSlot, 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;
|
||||
/* ── 2. ANGREIFEN (sofort nach der Bewegung) ────────── */
|
||||
const range = card.range ?? 0;
|
||||
|
||||
for (let r = 1; r <= range; r++) {
|
||||
const targetPos = currentPos + dir * 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) continue; // leeres Feld → weiter scannen
|
||||
if (target.owner === activeSlot) continue; // eigene Karte → Range geht hindurch
|
||||
|
||||
// Feindliche Karte → Angriff
|
||||
// Feindliche Karte gefunden → Angriff
|
||||
const atk = card.attack ?? 0;
|
||||
target.card = { ...target.card, defends: (target.card.defends ?? 0) - atk };
|
||||
|
||||
events.push({
|
||||
type : 'attack',
|
||||
from : slotId,
|
||||
from : currentSlotId,
|
||||
to : targetSlotId,
|
||||
damage : atk,
|
||||
remainingDef: target.card.defends,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user