diff --git a/PLAN.md b/PLAN.md index 79c76a0..a5b7faa 100644 --- a/PLAN.md +++ b/PLAN.md @@ -197,3 +197,6 @@ Abgabe: **9. Dezember 2026, 03:00 MEZ**. Eingereicht wird in „Best Daily Game „All 3 stars! Keep going for more gold – or end the game.“ - Erreichte Ziele werden **golden** hervorgehoben (wie `goal-chip.done` im Hauptspiel). - `devvit playtest` trägt `dev.subreddit` (`ninelands_game_dev`) in `devvit.json` ein. +- Rückmeldung Besitzer: Bei „One farm per land“ bleiben am Ende oft nur Ritter, die Gold kosten. Darum prüft der + Client nach jedem Zug, ob noch ein Zug Gold bringt (`gainLeft`, alle gültigen Züge per `preview`, < 1 ms). Wenn + nicht: Hinweis „No move left gains gold – end the game to keep what you have.“ und „End game“ leuchtet golden. diff --git a/src/client/game.css b/src/client/game.css index c2f6cb0..d05d2ea 100644 --- a/src/client/game.css +++ b/src/client/game.css @@ -181,6 +181,23 @@ button.end { white-space: nowrap; } +button.end.suggest { + background: var(--px-gold); + animation: pulse 1.2s steps(2) infinite; +} + +@keyframes pulse { + 50% { + outline: 2px solid var(--px-parchment); + } +} + +@media (prefers-reduced-motion: reduce) { + button.end.suggest { + animation: none; + } +} + button.end.armed { background: var(--px-red); color: var(--px-parchment); diff --git a/src/client/game.ts b/src/client/game.ts index b628e24..7af92be 100644 --- a/src/client/game.ts +++ b/src/client/game.ts @@ -260,6 +260,11 @@ function update(): void { bothGoal.classList.toggle('done', stars.both) undoBtn.disabled = finished || !game.canUndo endBtn.disabled = finished || game.moves.length === 0 + gainCache = null + endBtn.classList.toggle( + 'suggest', + !finished && game.moves.length > 0 && !gainLeft(), + ) const hasCastle = game.count('HIVEMOTHER') > 0 for (const [unit, btn] of toolButtons) { btn.classList.toggle('active', unit === tool) @@ -272,6 +277,23 @@ function update(): void { renderHint() } +/** Gibt es noch einen Zug, der Gold bringt? (je Spielstand einmal berechnet) */ +let gainCache: boolean | null = null +function gainLeft(): boolean { + if (gainCache !== null) return gainCache + gainCache = game.moves.length === 0 + for (const unit of UNIT_TYPES) { + if (gainCache) break + const legal = game.legalMoves(unit) + for (let cell = 0; cell < legal.length && !gainCache; cell++) { + if (!legal[cell]) continue + const p = game.preview(unit, cell) + if (p.ok && p.net > 0) gainCache = true + } + } + return gainCache +} + /** Feld, für das gerade eine Vorschau gilt: angetippt (Touch) oder unter der Maus */ function activeCell(): number | null { return pendingTouch ?? hovered @@ -320,7 +342,8 @@ function renderHint(): void { ) return } - if (starsOf(game, day.goals).both) setHint(HINTS.allStars, 'good') + if (!gainLeft()) setHint(HINTS.noGain, 'warn') + else if (starsOf(game, day.goals).both) setHint(HINTS.allStars, 'good') else if (game.count('HIVEMOTHER') === 0) setHint(HINTS.castle) else if (game.moves.length < 3) setHint(HINTS.farmer) else setHint(HINTS.general) diff --git a/src/client/texts.ts b/src/client/texts.ts index 2b36f6f..d49c628 100644 --- a/src/client/texts.ts +++ b/src/client/texts.ts @@ -114,6 +114,7 @@ export const HINTS = { general: 'Ore ⛰ and herbs 🌿 pay 20 gold. Knights pay double. Keep going until no move is left.', touch: 'Tap again to place.', + noGain: 'No move left gains gold – end the game to keep what you have.', allStars: 'All 3 stars! Keep going for more gold – or end the game.', } as const