feat: Hex Wars - Server-Scores, Minenexplosion, Dominanzsieg, Regel-Update
This commit is contained in:
@@ -31,11 +31,11 @@ function HelpModal({ onClose }) {
|
||||
</div>
|
||||
<div style={{ color:'rgba(255,255,255,0.75)', fontSize:13, lineHeight:1.9, fontFamily:'monospace' }}>
|
||||
<p style={{ marginTop:0 }}>
|
||||
<strong style={{ color:MY_COLOR }}>Ziel:</strong> Sammle am Ende mehr <strong>Punkte</strong> als dein Gegner.
|
||||
<strong style={{ color:MY_COLOR }}>Ziel:</strong> Sammle mehr <strong>Punkte</strong> als dein Gegner — durch clevere Expansion und das Meiden von Fallen.
|
||||
</p>
|
||||
|
||||
<p><strong style={{ color:'#fff' }}>Ablauf:</strong><br/>
|
||||
Beide starten in einer Ecke. Reihum wählst du ein <strong>graues Feld</strong> das an dein Gebiet grenzt (auch diagonal) und besetzt es.
|
||||
Beide starten in gegenüberliegenden Ecken. Reihum wählst du ein freies Feld das an dein Gebiet grenzt (auch diagonal) und besetzt es. Tippe es an, bestätige — fertig.
|
||||
</p>
|
||||
|
||||
<div style={{ background:'rgba(255,255,255,0.04)', borderRadius:8, padding:'12px 14px', marginBottom:12, border:'1px solid rgba(255,255,255,0.08)' }}>
|
||||
@@ -43,23 +43,23 @@ function HelpModal({ onClose }) {
|
||||
<div style={{ display:'grid', gridTemplateColumns:'28px 1fr', gap:'6px 10px', alignItems:'center' }}>
|
||||
<span style={{ fontSize:16, textAlign:'center' }}>⬜</span><span><strong style={{ color:'rgba(255,255,255,0.9)' }}>Normales Feld</strong> — +1 Punkt</span>
|
||||
<span style={{ fontSize:16, textAlign:'center' }}>⭐</span><span><strong style={{ color:'#ffe66d' }}>Goldfeld</strong> — +3 Punkte, sehr wertvoll!</span>
|
||||
<span style={{ fontSize:16, textAlign:'center' }}>💣</span><span><strong style={{ color:'#ff6b9d' }}>Sprengmine</strong> — −3 Punkte & du verlierst deinen nächsten Zug</span>
|
||||
<span style={{ fontSize:16, textAlign:'center' }}>💣</span><span><strong style={{ color:'#ff6b9d' }}>Sprengmine</strong> — Betreten zündet eine Explosion: alle 8 umliegenden Felder werden zerstört. Besetzte Felder gehen verloren, du verlierst deinen nächsten Zug.</span>
|
||||
<span style={{ fontSize:16, textAlign:'center' }}>🪨</span><span><strong style={{ color:'rgba(255,255,255,0.4)' }}>Felsen</strong> — unbesetzbar, blockiert Wege</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
<strong style={{ color:'#fff' }}>🌫️ Nebel:</strong><br/>
|
||||
Du siehst nur dein Gebiet und die direkt angrenzenden Felder. Was dahinter liegt bleibt verborgen — auch Minen und Goldfelder. Erst wenn du nah genug rankommst wird es aufgedeckt.
|
||||
Du siehst nur dein Gebiet und die direkt angrenzenden Felder. Goldfelder und Minen bleiben verborgen bis du nahe genug herankommst — also aufgepasst!
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong style={{ color:'#fff' }}>Ende:</strong><br/>
|
||||
Wenn niemand mehr ziehen kann, gewinnt wer mehr Punkte hat. Tippe ein Feld an und bestätige deinen Zug — du kriegst eine <strong>Pushover-Benachrichtigung</strong> wenn du dran bist.
|
||||
<strong style={{ color:'#fff' }}>Spielende:</strong><br/>
|
||||
Das Spiel endet wenn niemand mehr ziehen kann — oder wenn jemand <strong>doppelt so viele Punkte</strong> hat wie der Gegner (Dominanzsieg 👑). Wer mehr Punkte hat, gewinnt.
|
||||
</p>
|
||||
|
||||
<div style={{ background:'rgba(255,255,255,0.04)', borderRadius:8, padding:'10px 14px', border:'1px solid rgba(255,255,255,0.08)' }}>
|
||||
📱 <em>Auf dem Handy: Pinch zum Zoomen, dann Feld antippen und bestätigen.</em>
|
||||
📱 <em>Auf dem Handy: Pinch zum Zoomen, Feld antippen und bestätigen.</em>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -151,11 +151,21 @@ function GameBoard({ game, myId, onMove, toast }) {
|
||||
const oppName = game.owner_id === myId ? game.opp_name : game.owner_name;
|
||||
|
||||
// Mine-Event anzeigen wenn frisch
|
||||
const [blastHighlight, setBlastHighlight] = useState(new Set());
|
||||
|
||||
useEffect(() => {
|
||||
if (!game.last_event) return;
|
||||
try {
|
||||
const ev = JSON.parse(game.last_event);
|
||||
if (ev.type === 'mine') setMineAlert(ev);
|
||||
if (ev.type === 'mine') {
|
||||
setMineAlert(ev);
|
||||
// Blast-Zellen kurz highlighten
|
||||
if (ev.blastCells?.length) {
|
||||
const keys = new Set(ev.blastCells.map(([r,c]) => `${r}-${c}`));
|
||||
setBlastHighlight(keys);
|
||||
setTimeout(() => setBlastHighlight(new Set()), 2000);
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
}, [game.last_event]);
|
||||
|
||||
@@ -190,30 +200,19 @@ function GameBoard({ game, myId, onMove, toast }) {
|
||||
} finally { setMoving(false); }
|
||||
};
|
||||
|
||||
// Punkte berechnen (clientseitig, nur aus sichtbaren Feldern)
|
||||
const calcVisible = (playerId) => {
|
||||
let score = 0;
|
||||
for (let r=0;r<GRID;r++) for (let c=0;c<GRID;c++) {
|
||||
if (grid[r][c] !== playerId) continue;
|
||||
const t = hasTerrain ? terrain[r][c] : T_NORMAL;
|
||||
if (t === T_GOLD) score += 3;
|
||||
else if (t === T_MINE) score -= 3;
|
||||
else score += 1;
|
||||
}
|
||||
return Math.max(0, score);
|
||||
};
|
||||
|
||||
const myScore = calcVisible(myId);
|
||||
const oppScore = calcVisible(opponent);
|
||||
// Scores kommen vom Server (auf ungemasktem Grid berechnet — korrekt für beide Spieler)
|
||||
const myScore = game.myScore ?? 0;
|
||||
const oppScore = game.oppScore ?? 0;
|
||||
const myCount = grid.flat().filter(v => v === myId).length;
|
||||
const oppCount = grid.flat().filter(v => v === opponent).length;
|
||||
const fogCount = grid.flat().filter(v => v === 0 && hasTerrain).length;
|
||||
|
||||
let banner = null;
|
||||
if (game.status === 'finished') {
|
||||
if (!game.winner_id) banner = { text:'Unentschieden! 🤝', color:'#ffe66d' };
|
||||
else if (game.winner_id === myId) banner = { text:'Du hast gewonnen! 🎉', color:MY_COLOR };
|
||||
else banner = { text:'Du hast verloren.', color:OPP_COLOR };
|
||||
const isDominance = myScore >= oppScore * 2 || oppScore >= myScore * 2;
|
||||
if (!game.winner_id) banner = { text:'Unentschieden! 🤝', color:'#ffe66d' };
|
||||
else if (game.winner_id === myId) banner = { text: isDominance ? 'Dominanzsieg! 👑🎉' : 'Du hast gewonnen! 🎉', color:MY_COLOR };
|
||||
else banner = { text: isDominance ? 'Dominanzniederlage 💀' : 'Du hast verloren.', color:OPP_COLOR };
|
||||
}
|
||||
|
||||
const vw = Math.min(window.innerWidth, 600);
|
||||
@@ -229,6 +228,7 @@ function GameBoard({ game, myId, onMove, toast }) {
|
||||
const t = hasTerrain ? terrain[r][c] : T_NORMAL;
|
||||
const clic = canClick(r, c);
|
||||
const isPend = pending?.row === r && pending?.col === c;
|
||||
const isBlast = blastHighlight.has(`${r}-${c}`);
|
||||
const isFog = t === T_FOG;
|
||||
const isRock = t === T_ROCK;
|
||||
const isGold = t === T_GOLD;
|
||||
@@ -256,8 +256,9 @@ function GameBoard({ game, myId, onMove, toast }) {
|
||||
else if (clic) bg = `${myColor}22`;
|
||||
}
|
||||
|
||||
if (isBlast) { bg = '#ff440066'; border = '2px solid #ff4400'; }
|
||||
if (isPend) { bg = myColor; border = `2px solid ${myColor}`; }
|
||||
else if (clic && !isPend) border = `1px solid ${myColor}44`;
|
||||
else if (clic && !isPend && !isBlast) border = `1px solid ${myColor}44`;
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -299,8 +300,8 @@ function GameBoard({ game, myId, onMove, toast }) {
|
||||
<span style={{ fontSize:20 }}>💣</span>
|
||||
<span style={{ color:'#ff6b9d', fontFamily:'monospace', fontSize:12, flex:1 }}>
|
||||
{mineAlert.player === myId
|
||||
? 'Du hast eine Mine getroffen! −3 Punkte & Zug verloren.'
|
||||
: `${oppName} hat eine Mine getroffen! 😈`}
|
||||
? `💥 Mine! ${mineAlert.destroyedOwn ? mineAlert.destroyedOwn + ' deiner Felder zerstört. ' : ''}Zug verloren.`
|
||||
: `💥 ${oppName} trat auf eine Mine! ${mineAlert.destroyedOpp ? mineAlert.destroyedOpp + ' deiner Felder zerstört!' : 'Kein Schaden.'} 😈`}
|
||||
</span>
|
||||
<button onClick={() => setMineAlert(null)} style={{ ...S.btn('#666666', true), padding:'2px 8px' }}>✕</button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user