From afd2aa602943fc9bd948519021b77295873ab9ae Mon Sep 17 00:00:00 2001 From: Dicken Date: Fri, 26 Jun 2026 16:30:21 +0200 Subject: [PATCH] fix: Hex Wars - Felsen im Nebel, kein Scrollen auf Mobile, Leaderboard-Refresh --- backend/src/tools/gebietseroberung/routes.js | 3 +- frontend/src/tools/gebietseroberung.jsx | 73 +++++++++----------- 2 files changed, 34 insertions(+), 42 deletions(-) diff --git a/backend/src/tools/gebietseroberung/routes.js b/backend/src/tools/gebietseroberung/routes.js index ae8483f..02d5bd5 100644 --- a/backend/src/tools/gebietseroberung/routes.js +++ b/backend/src/tools/gebietseroberung/routes.js @@ -160,8 +160,7 @@ function getGameForUser(id, requesterId) { // Grid maskieren: versteckte Gegner-Felder als 0 zeigen, Terrain nur wo sichtbar const maskedGrid = grid.map((row,r) => row.map((cell,c) => visible[r][c] ? cell : (cell === requesterId ? cell : 0))); - // Felsen sind immer sichtbar (Frontend braucht sie für canClick) - const maskedTerrain = terrain.map((row,r) => row.map((cell,c) => (visible[r][c] || cell === T_ROCK) ? cell : -1)); + const maskedTerrain = terrain.map((row,r) => row.map((cell,c) => visible[r][c] ? cell : -1)); return { ...game, grid: JSON.stringify(maskedGrid), terrain: JSON.stringify(maskedTerrain), fog: JSON.stringify(visible) }; } diff --git a/frontend/src/tools/gebietseroberung.jsx b/frontend/src/tools/gebietseroberung.jsx index a573c2a..f2a64d0 100644 --- a/frontend/src/tools/gebietseroberung.jsx +++ b/frontend/src/tools/gebietseroberung.jsx @@ -111,9 +111,9 @@ function NewGameModal({ onClose, onCreated, toast }) { } // ── Topliste ────────────────────────────────────────────────────────────────── -function Leaderboard() { +function Leaderboard({ reloadKey }) { const [rows, setRows] = useState(null); - useEffect(() => { api('/tools/gebietseroberung/leaderboard').then(setRows).catch(() => setRows([])); }, []); + useEffect(() => { api('/tools/gebietseroberung/leaderboard').then(setRows).catch(() => setRows([])); }, [reloadKey]); if (!rows) return null; const medals = ['🥇','🥈','🥉']; return ( @@ -216,8 +216,12 @@ function GameBoard({ game, myId, onMove, toast }) { else banner = { text:'Du hast verloren.', color:OPP_COLOR }; } - const vw = Math.min(window.innerWidth, 600); - const cellSize = Math.max(14, Math.floor((vw - 32) / GRID)); + const vw = Math.min(window.innerWidth, 600); + // Verfügbare Höhe: Viewport minus Header (~48px) minus Scoreboard (~72px) minus Status (~40px) minus Legende (~24px) minus Puffer (20px) + const availH = window.innerHeight - 204; + const byWidth = Math.floor((vw - 32) / GRID); + const byHeight = Math.floor(availH / GRID); + const cellSize = Math.max(10, Math.min(byWidth, byHeight)); // Zell-Rendering const renderCell = (r, c) => { @@ -273,20 +277,15 @@ function GameBoard({ game, myId, onMove, toast }) { return (
{/* Scoreboard */} -
-
-
DU
-
{myScore}
-
{myCount} Felder
+
+
+
DU
+
{myScore} Pkt
-
-
Pkt
-
🌫️ {fogCount}
-
-
-
{oppName?.toUpperCase()}
-
{oppScore}
-
{oppCount} Felder
+
🌫️{fogCount}
+
+
{oppName?.toUpperCase()}
+
Pkt {oppScore}
@@ -319,37 +318,31 @@ function GameBoard({ game, myId, onMove, toast }) { {/* Zug-Status */} {game.status === 'active' && ( -
- {!isMyTurn && ( -
+
+ {!isMyTurn && !pending && ( +
⏳ {oppName} ist dran…
)} {isMyTurn && !pending && ( -
+
▶ Wähle ein Feld — Vorsicht vor Minen! 💣
)} - {isMyTurn && pending && ( -
- - ✦ Zeile {pending.row+1}, Spalte {pending.col+1} — Zug bestätigen? + {pending && ( +
+ + ✦ R{pending.row+1} S{pending.col+1} — bestätigen? - +
)}
)} {/* Legende */} -
+
{[['⭐','Gold +3','#ffe66d'],['💣','Mine −3','#ff6b9d'],['🪨','Felsen','rgba(255,255,255,0.35)'],['🌫️','Nebel','rgba(255,255,255,0.25)']].map(([icon,label,color]) => (
{icon} @@ -364,9 +357,7 @@ function GameBoard({ game, myId, onMove, toast }) { {grid.map((row, r) => row.map((_, c) => renderCell(r, c)))}
-
- 📱 Pinch zum Zoomen -
+
); } @@ -407,7 +398,7 @@ function GameRow({ g, myId, isAdmin, onSelect, onDelete }) { } // ── Spielliste ──────────────────────────────────────────────────────────────── -function GameList({ games, myId, isAdmin, onSelect, onNew, onDelete }) { +function GameList({ games, myId, isAdmin, onSelect, onNew, onDelete, reloadKey }) { const active = games.filter(g => g.status === 'active'); const finished = games.filter(g => g.status === 'finished' && (g.move_count || 0) >= 2); return ( @@ -428,7 +419,7 @@ function GameList({ games, myId, isAdmin, onSelect, onNew, onDelete }) { Noch keine Spiele — starte eines!
)} - +
); } @@ -442,6 +433,7 @@ export default function HexWars({ toast }) { const [showHelp, setShowHelp] = useState(false); const [loading, setLoading] = useState(true); + const [reloadKey, setReloadKey] = useState(0); const myId = getMyId(); const isAdmin = getMyRole() === 'admin'; @@ -491,6 +483,7 @@ export default function HexWars({ toast }) { try { await api(`/tools/gebietseroberung/${id}`, { method: 'DELETE' }); setGames(prev => prev.filter(g => g.id !== id)); + setReloadKey(k => k + 1); toast('Spiel gelöscht.'); } catch(e) { toast?.(e.message || 'Fehler', 'error'); } }; @@ -499,7 +492,7 @@ export default function HexWars({ toast }) { return (
-
+
{activeId && ( )} @@ -514,7 +507,7 @@ export default function HexWars({ toast }) {
{!activeId - ? setShowNew(true)} onDelete={handleDelete} /> + ? setShowNew(true)} onDelete={handleDelete} reloadKey={reloadKey} /> : game && myId ? :
Lade Spiel…