sfrthrt
This commit is contained in:
parent
33460b025d
commit
75884af315
@ -1196,3 +1196,88 @@ body {
|
|||||||
0 0 calc(var(--s) * 42) rgba(220, 60, 60, 0.75),
|
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;
|
inset 0 0 calc(var(--s) * 20) rgba(0, 0, 0, 0.5) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ══════════════════════════════════════════════════════
|
||||||
|
ABLAGESTAPEL (Discard Zone)
|
||||||
|
══════════════════════════════════════════════════════ */
|
||||||
|
.hand-slot-discard {
|
||||||
|
position: relative;
|
||||||
|
width: calc(var(--s) * 105);
|
||||||
|
height: calc(var(--s) * 160);
|
||||||
|
border-radius: calc(var(--s) * 9);
|
||||||
|
border: 2px dashed rgba(180, 80, 60, 0.4);
|
||||||
|
background: rgba(20, 8, 5, 0.75);
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: default;
|
||||||
|
transition: border-color 0.2s, background 0.2s;
|
||||||
|
box-shadow: 0 calc(var(--s) * 4) calc(var(--s) * 14) rgba(0,0,0,0.35);
|
||||||
|
margin-right: calc(var(--s) * 8);
|
||||||
|
}
|
||||||
|
.hand-slot-discard.drag-over {
|
||||||
|
border-color: rgba(220, 60, 60, 0.95);
|
||||||
|
background: rgba(80, 15, 10, 0.7);
|
||||||
|
box-shadow: 0 0 calc(var(--s) * 20) rgba(220,60,60,0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.discard-stack-wrap {
|
||||||
|
position: absolute;
|
||||||
|
inset: calc(var(--s) * 8) calc(var(--s) * 8) calc(var(--s) * 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Gestapelte Karten-Rückseiten im Ablagestapel */
|
||||||
|
.discard-card-back {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: calc(var(--s) * 7);
|
||||||
|
filter: grayscale(70%) brightness(0.55);
|
||||||
|
box-shadow: 0 2px 6px rgba(0,0,0,0.6);
|
||||||
|
}
|
||||||
|
.discard-card-back:nth-child(1) { transform: translate(-3px, -3px) rotate(-4deg); }
|
||||||
|
.discard-card-back:nth-child(2) { transform: translate(-1px, -1px) rotate(-1.5deg); }
|
||||||
|
.discard-card-back:nth-child(3) { transform: translate(0, 0) rotate(1deg); }
|
||||||
|
|
||||||
|
.discard-label {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
font-family: "Cinzel", serif;
|
||||||
|
font-size: calc(var(--s) * 9);
|
||||||
|
color: rgba(200, 100, 80, 0.6);
|
||||||
|
letter-spacing: calc(var(--s) * 1);
|
||||||
|
pointer-events: none;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.discard-stack-wrap:not(:empty) + .discard-label {
|
||||||
|
display: none; /* Label verstecken wenn Karten drin */
|
||||||
|
}
|
||||||
|
|
||||||
|
.discard-count {
|
||||||
|
position: absolute;
|
||||||
|
bottom: calc(var(--s) * 3);
|
||||||
|
left: 0; right: 0;
|
||||||
|
text-align: center;
|
||||||
|
font-family: "Cinzel", serif;
|
||||||
|
font-size: calc(var(--s) * 10);
|
||||||
|
color: rgba(200, 100, 80, 0.75);
|
||||||
|
text-shadow: 0 1px 3px rgba(0,0,0,0.8);
|
||||||
|
}
|
||||||
|
.discard-count:empty,
|
||||||
|
.discard-count[data-count="0"] { opacity: 0; }
|
||||||
|
|
||||||
|
/* Board-Karte draggable während eigenem Zug */
|
||||||
|
.board-slot-draggable {
|
||||||
|
cursor: grab !important;
|
||||||
|
}
|
||||||
|
.board-slot-draggable:active {
|
||||||
|
cursor: grabbing !important;
|
||||||
|
}
|
||||||
|
.board-slot-draggable.dragging {
|
||||||
|
opacity: 0.45;
|
||||||
|
}
|
||||||
|
|||||||
@ -233,6 +233,7 @@ function updateTimerUI(secs, activeName) {
|
|||||||
═══════════════════════════════════════════════════════════ */
|
═══════════════════════════════════════════════════════════ */
|
||||||
function setTurnState(myTurn, activeName) {
|
function setTurnState(myTurn, activeName) {
|
||||||
isMyTurn = myTurn;
|
isMyTurn = myTurn;
|
||||||
|
if (myTurn) enableBoardDrag(); else disableBoardDrag();
|
||||||
const btn = document.getElementById('end-turn-btn');
|
const btn = document.getElementById('end-turn-btn');
|
||||||
if (!btn) return;
|
if (!btn) return;
|
||||||
handCardIds.forEach(id => renderHandSlot(id));
|
handCardIds.forEach(id => renderHandSlot(id));
|
||||||
@ -652,6 +653,130 @@ document.getElementById('handArea').addEventListener('dragend', e => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* ═══════════════════════════════════════════════════════════
|
||||||
|
ABLAGESTAPEL – DISCARD SYSTEM
|
||||||
|
Spieler zieht Karte vom Board → auf Discard-Zone droppen
|
||||||
|
→ Karte weg vom Board, ausgegraut auf Stapel, nicht mehr spielbar
|
||||||
|
═══════════════════════════════════════════════════════════ */
|
||||||
|
|
||||||
|
const discardPile = []; // abgeworfene Karten (lokal)
|
||||||
|
|
||||||
|
/* ── Discard-Zone Drag-Events ────────────────────────────── */
|
||||||
|
const discardZone = document.getElementById('discard-zone');
|
||||||
|
|
||||||
|
discardZone.addEventListener('dragover', e => {
|
||||||
|
if (!isMyTurn) return;
|
||||||
|
if (!draggedBoardSlotId) return; // nur Board-Karten
|
||||||
|
e.preventDefault();
|
||||||
|
discardZone.classList.add('drag-over');
|
||||||
|
});
|
||||||
|
|
||||||
|
discardZone.addEventListener('dragleave', () => {
|
||||||
|
discardZone.classList.remove('drag-over');
|
||||||
|
});
|
||||||
|
|
||||||
|
discardZone.addEventListener('drop', e => {
|
||||||
|
e.preventDefault();
|
||||||
|
discardZone.classList.remove('drag-over');
|
||||||
|
if (!draggedBoardSlotId || !isMyTurn) return;
|
||||||
|
discardBoardCard(draggedBoardSlotId);
|
||||||
|
draggedBoardSlotId = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
/* ── Board-Karten draggable machen / entfernen ───────────── */
|
||||||
|
let draggedBoardSlotId = null;
|
||||||
|
|
||||||
|
function enableBoardDrag() {
|
||||||
|
document.querySelectorAll('.card-slot').forEach(slot => {
|
||||||
|
const idx = Number(slot.dataset.slotIndex);
|
||||||
|
if (!isMyZone(idx)) return;
|
||||||
|
if (!boardState[slot.id]) return;
|
||||||
|
if (boardState[slot.id].owner !== mySlot) return;
|
||||||
|
|
||||||
|
slot.classList.add('board-slot-draggable');
|
||||||
|
slot.setAttribute('draggable', 'true');
|
||||||
|
|
||||||
|
// Event-Listener nur einmal anhängen
|
||||||
|
if (!slot.dataset.discardListenerAttached) {
|
||||||
|
slot.dataset.discardListenerAttached = '1';
|
||||||
|
slot.addEventListener('dragstart', e => {
|
||||||
|
if (!isMyTurn) { e.preventDefault(); return; }
|
||||||
|
draggedBoardSlotId = slot.id;
|
||||||
|
slot.classList.add('dragging');
|
||||||
|
e.dataTransfer.effectAllowed = 'move';
|
||||||
|
e.dataTransfer.setData('text/board', slot.id);
|
||||||
|
});
|
||||||
|
slot.addEventListener('dragend', () => {
|
||||||
|
slot.classList.remove('dragging');
|
||||||
|
draggedBoardSlotId = null;
|
||||||
|
discardZone.classList.remove('drag-over');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function disableBoardDrag() {
|
||||||
|
document.querySelectorAll('.board-slot-draggable').forEach(slot => {
|
||||||
|
slot.classList.remove('board-slot-draggable');
|
||||||
|
slot.setAttribute('draggable', 'false');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Karte abwerfen ──────────────────────────────────────── */
|
||||||
|
function discardBoardCard(slotId) {
|
||||||
|
const entry = boardState[slotId];
|
||||||
|
if (!entry || entry.owner !== mySlot) return;
|
||||||
|
|
||||||
|
const card = entry.card;
|
||||||
|
discardPile.push(card);
|
||||||
|
delete boardState[slotId];
|
||||||
|
|
||||||
|
// Slot leeren
|
||||||
|
const slotEl = document.getElementById(slotId);
|
||||||
|
if (slotEl) {
|
||||||
|
slotEl.innerHTML = '';
|
||||||
|
slotEl.classList.remove('board-slot-draggable', 'dragging');
|
||||||
|
slotEl.setAttribute('draggable', 'false');
|
||||||
|
delete slotEl.dataset.discardListenerAttached;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateDiscardZone();
|
||||||
|
|
||||||
|
socket.emit('discard_card', { matchId, slotId, slot: mySlot, cardId: card.id });
|
||||||
|
console.log(`[Discard] Karte abgeworfen: ${card.name} von ${slotId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Discard-Zone visuell updaten ────────────────────────── */
|
||||||
|
function updateDiscardZone() {
|
||||||
|
const stack = document.getElementById('discard-stack');
|
||||||
|
const count = document.getElementById('discard-count');
|
||||||
|
if (!stack) return;
|
||||||
|
|
||||||
|
const shown = Math.min(discardPile.length, 3);
|
||||||
|
stack.innerHTML = Array.from({ length: shown }).map(() =>
|
||||||
|
`<img class="discard-card-back" src="/images/items/rueckseite.png" alt="Abgeworfen" draggable="false">`
|
||||||
|
).join('');
|
||||||
|
|
||||||
|
if (count) {
|
||||||
|
count.textContent = discardPile.length > 0 ? discardPile.length : '';
|
||||||
|
count.dataset.count = discardPile.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Gegner wirft Karte ab → Slot leeren ─────────────────── */
|
||||||
|
socket.on('card_discarded', data => {
|
||||||
|
const { slotId } = data;
|
||||||
|
if (!slotId) return;
|
||||||
|
const slotEl = document.getElementById(slotId);
|
||||||
|
if (slotEl) slotEl.innerHTML = '';
|
||||||
|
delete boardState[slotId];
|
||||||
|
console.log(`[Discard] Gegner hat Karte abgeworfen von ${slotId}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
/* ── Board-Drag bei Zugwechsel aktivieren/deaktivieren ────── */
|
||||||
|
// Wird von setTurnState aufgerufen (siehe unten)
|
||||||
socket.on('card_played', data => {
|
socket.on('card_played', data => {
|
||||||
if (data.slot === mySlot) return;
|
if (data.slot === mySlot) return;
|
||||||
if (boardState[data.boardSlot]) return;
|
if (boardState[data.boardSlot]) return;
|
||||||
|
|||||||
@ -153,14 +153,23 @@ async function askHaiku(boardState, aiHand, availableSlots, aiSlot, leftSlot) {
|
|||||||
defends: c.defends ?? 0, race: c.race ?? 0, range: c.range ?? 1,
|
defends: c.defends ?? 0, race: c.race ?? 0, range: c.range ?? 1,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// KI-eigene Karten auf dem Board (könnten abgeworfen werden)
|
||||||
|
const myBoardCards = Object.entries(boardState)
|
||||||
|
.filter(([, e]) => e.owner === aiSlot)
|
||||||
|
.map(([slot, e]) => ({ slot, name: e.card.name, atk: e.card.attack ?? 0 }));
|
||||||
|
|
||||||
const prompt = `Kartenspiel. Du bist die KI (${isLeft ? 'linke Seite, Slots 1-3, bewegst dich nach rechts' : 'rechte Seite, Slots 9-11, bewegst dich nach links'}).
|
const prompt = `Kartenspiel. Du bist die KI (${isLeft ? 'linke Seite, Slots 1-3, bewegst dich nach rechts' : 'rechte Seite, Slots 9-11, bewegst dich nach links'}).
|
||||||
Board: ${JSON.stringify(boardSummary)}
|
Board: ${JSON.stringify(boardSummary)}
|
||||||
|
Deine eigenen Karten auf dem Board: ${JSON.stringify(myBoardCards)}
|
||||||
Deine Hand: ${JSON.stringify(handSummary)}
|
Deine Hand: ${JSON.stringify(handSummary)}
|
||||||
Freie Slots: ${JSON.stringify(availableSlots)}
|
Freie Slots: ${JSON.stringify(availableSlots)}
|
||||||
|
|
||||||
Wähle eine Karte und einen freien Slot. Bevorzuge Karten mit hohem Angriff oder hoher Bewegung (race).
|
Optionen:
|
||||||
Antworte NUR mit validem JSON: {"card_index":0,"slot":"row1-slot-9"}
|
1. Karte ausspielen: {"card_index":0,"slot":"row1-slot-9"}
|
||||||
Wenn keine Karte oder kein Slot: {"skip":true}`;
|
2. Eigene Boardkarte abwerfen (wenn sie schlecht positioniert ist): {"discard_slot":"row1-slot-9"}
|
||||||
|
3. Nichts tun: {"skip":true}
|
||||||
|
|
||||||
|
Antworte NUR mit validem JSON.`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch('https://api.anthropic.com/v1/messages', {
|
const res = await fetch('https://api.anthropic.com/v1/messages', {
|
||||||
@ -213,7 +222,20 @@ async function playAiTurn(io, matchId) {
|
|||||||
aiRoom.boardState, readyCards, freeSlots, aiRoom.aiSlot, aiRoom.leftSlot
|
aiRoom.boardState, readyCards, freeSlots, aiRoom.aiSlot, aiRoom.leftSlot
|
||||||
);
|
);
|
||||||
|
|
||||||
if (decision && !decision.skip && decision.card_index != null && decision.slot) {
|
// KI wirft eigene Boardkarte ab
|
||||||
|
if (decision && decision.discard_slot) {
|
||||||
|
const dsSlot = decision.discard_slot;
|
||||||
|
if (aiRoom.boardState[dsSlot]?.owner === aiRoom.aiSlot) {
|
||||||
|
delete aiRoom.boardState[dsSlot];
|
||||||
|
const ioRoom = io._arenaRooms?.get(matchId);
|
||||||
|
if (ioRoom?.boardState) delete ioRoom.boardState[dsSlot];
|
||||||
|
io.to(aiRoom.playerSocketId).emit('card_discarded', { slotId: dsSlot });
|
||||||
|
console.log(`[HT] KI wirft ab: ${dsSlot}`);
|
||||||
|
await sleep(400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decision && !decision.skip && !decision.discard_slot && decision.card_index != null && decision.slot) {
|
||||||
const card = readyCards[decision.card_index];
|
const card = readyCards[decision.card_index];
|
||||||
const slotId = decision.slot;
|
const slotId = decision.slot;
|
||||||
|
|
||||||
@ -514,6 +536,23 @@ function registerHimmelstorHandlers(io, socket) {
|
|||||||
console.log(`[HT] arena_join KI-Match: ${matchId}`);
|
console.log(`[HT] arena_join KI-Match: ${matchId}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
/* ── Spieler wirft Karte ab (KI-Match) ───────────────────── */
|
||||||
|
socket.on('discard_card', (data) => {
|
||||||
|
const { matchId, slotId, slot } = data;
|
||||||
|
if (!matchId || !htAiMatchIds.has(matchId)) return;
|
||||||
|
|
||||||
|
const aiRoom = htAiRooms.get(matchId);
|
||||||
|
if (!aiRoom || aiRoom.gameOver) return;
|
||||||
|
if (slot !== aiRoom.playerSlot) return; // nur eigene Karten
|
||||||
|
|
||||||
|
if (aiRoom.boardState) delete aiRoom.boardState[slotId];
|
||||||
|
const ioRoom = io._arenaRooms?.get(matchId);
|
||||||
|
if (ioRoom?.boardState) delete ioRoom.boardState[slotId];
|
||||||
|
|
||||||
|
console.log(`[HT] Spieler wirft ab: ${slotId} | Match ${matchId}`);
|
||||||
|
});
|
||||||
|
|
||||||
/* ── end_turn für KI-Matches ────────────────────────────── */
|
/* ── end_turn für KI-Matches ────────────────────────────── */
|
||||||
socket.on('end_turn', async (data) => {
|
socket.on('end_turn', async (data) => {
|
||||||
const { matchId, slot } = data;
|
const { matchId, slot } = data;
|
||||||
|
|||||||
@ -817,6 +817,30 @@ function registerArenaHandlers(io, socket) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
/* ── Zug beenden → Kampfphase → Zugwechsel ── */
|
/* ── Zug beenden → Kampfphase → Zugwechsel ── */
|
||||||
|
|
||||||
|
/* ── Karte vom Board abwerfen ────────────────────────────── */
|
||||||
|
socket.on('discard_card', (data) => {
|
||||||
|
const { matchId, slotId, slot } = data;
|
||||||
|
if (!matchId || !slotId) return;
|
||||||
|
if (htAiMatchIds.has(matchId)) return; // KI-Matches: handled by himmelstor.socket
|
||||||
|
|
||||||
|
const room = io._arenaRooms?.get(matchId);
|
||||||
|
if (!room || room.gameOver) return;
|
||||||
|
if (room.sockets[slot] !== socket.id) return; // nur eigene Karten
|
||||||
|
|
||||||
|
// Karte aus boardState entfernen
|
||||||
|
if (room.boardState) delete room.boardState[slotId];
|
||||||
|
room.boardCards = room.boardCards?.filter(c => c.boardSlot !== slotId) ?? [];
|
||||||
|
|
||||||
|
// Gegner informieren
|
||||||
|
const oppSlot = slot === 'player1' ? 'player2' : 'player1';
|
||||||
|
const oppSocket = room.sockets[oppSlot];
|
||||||
|
if (oppSocket) {
|
||||||
|
io.to(oppSocket).emit('card_discarded', { slotId });
|
||||||
|
}
|
||||||
|
console.log(`[Arena] Karte abgeworfen: ${slotId} von ${slot} | Match ${matchId}`);
|
||||||
|
});
|
||||||
|
|
||||||
socket.on("end_turn", async (data) => {
|
socket.on("end_turn", async (data) => {
|
||||||
const { matchId, slot } = data;
|
const { matchId, slot } = data;
|
||||||
if (!matchId || !slot) return;
|
if (!matchId || !slot) return;
|
||||||
|
|||||||
@ -89,6 +89,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bottom-bar">
|
<div class="bottom-bar">
|
||||||
|
<!-- Ablagestapel: links neben dem Deck -->
|
||||||
|
<div id="discard-zone" class="hand-slot-discard" title="Karte abwerfen">
|
||||||
|
<div class="discard-stack-wrap" id="discard-stack"></div>
|
||||||
|
<div class="discard-label">Ablage</div>
|
||||||
|
<div class="discard-count" id="discard-count">0</div>
|
||||||
|
</div>
|
||||||
<div class="hand-area" id="handArea"></div>
|
<div class="hand-area" id="handArea"></div>
|
||||||
<div class="action-hud">
|
<div class="action-hud">
|
||||||
<div class="action-row">
|
<div class="action-row">
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user