Fix G key (playBestSolution never awaited async bestSolution), add menu exit

- playBestSolution() called bestSolution() (now async, hits the server)
  without await/async — got a Promise object back, .moves was undefined,
  autoPlayStep() bailed on its first tick. G silently did nothing.
- No way back to the menu from gameplay — add 🏠 HUD button + M key,
  cancels any in-flight search/autoplay before switching screens.
- settings-save button now shows "Сохраняю…" and disables itself during
  the request — previously gave no feedback while the fetch was in
  flight, looked like a dead button even though it worked (confirmed via
  server logs/DB — the save itself was never broken).
This commit is contained in:
Alex Cube
2026-08-10 15:39:54 +03:00
parent da2f6dd1f5
commit 9c5763c5bc
+25 -3
View File
@@ -13,6 +13,8 @@
canvas { border: 3px solid #e94560; border-radius: 12px; box-shadow: 0 0 20px rgba(233,69,96,0.4); } canvas { border: 3px solid #e94560; border-radius: 12px; box-shadow: 0 0 20px rgba(233,69,96,0.4); }
#hud { display: flex; gap: 20px; color: #e94560; font-size: 16px; font-weight: bold; } #hud { display: flex; gap: 20px; color: #e94560; font-size: 16px; font-weight: bold; }
#hud span { background: rgba(233,69,96,0.1); padding: 8px 16px; border-radius: 20px; border: 1px solid rgba(233,69,96,0.3); } #hud span { background: rgba(233,69,96,0.1); padding: 8px 16px; border-radius: 20px; border: 1px solid rgba(233,69,96,0.3); }
#menu-btn { cursor: pointer; }
#menu-btn:hover { background: rgba(233,69,96,0.25) !important; }
#controls { color: #aaa; font-size: 13px; } #controls { color: #aaa; font-size: 13px; }
kbd { background: #1a1a2e; border: 1px solid #e94560; border-radius: 4px; padding: 2px 6px; color: #e94560; } kbd { background: #1a1a2e; border: 1px solid #e94560; border-radius: 4px; padding: 2px 6px; color: #e94560; }
#solve-overlay { position: fixed; inset: 0; background: rgba(10,10,21,0.75); display: flex; justify-content: center; align-items: center; z-index: 10; } #solve-overlay { position: fixed; inset: 0; background: rgba(10,10,21,0.75); display: flex; justify-content: center; align-items: center; z-index: 10; }
@@ -124,6 +126,7 @@
<span id="level-name">Уровень 1</span> <span id="level-name">Уровень 1</span>
<span id="box-count">📦 0/0</span> <span id="box-count">📦 0/0</span>
<span id="moves-count">👣 0</span> <span id="moves-count">👣 0</span>
<span id="menu-btn" title="В меню">🏠</span>
</div> </div>
<div id="controls"> <div id="controls">
<kbd> </kbd> или <kbd>WASD</kbd> движение &nbsp; <kbd> </kbd> или <kbd>WASD</kbd> движение &nbsp;
@@ -131,7 +134,8 @@
<kbd>P</kbd> пред. &nbsp; <kbd>P</kbd> пред. &nbsp;
<kbd>R</kbd> рестарт &nbsp; <kbd>R</kbd> рестарт &nbsp;
<kbd>F</kbd> автопоиск (<kbd>Esc</kbd> отмена) &nbsp; <kbd>F</kbd> автопоиск (<kbd>Esc</kbd> отмена) &nbsp;
<kbd>G</kbd> лучшее сохранённое решение <kbd>G</kbd> лучшее сохранённое решение &nbsp;
<kbd>M</kbd> в меню
</div> </div>
</div> </div>
@@ -1722,9 +1726,9 @@ function autoPlayStep() {
tryMove(dx, dy); tryMove(dx, dy);
} }
function playBestSolution() { async function playBestSolution() {
if (autoSolveActive || levelComplete || solveState || playerMoving || boxes.some(b => b.moving)) return; if (autoSolveActive || levelComplete || solveState || playerMoving || boxes.some(b => b.moving)) return;
const best = bestSolution(LEVELS[currentLevel].name); const best = await bestSolution(LEVELS[currentLevel].name);
if (!best) { alert('Нет сохранённых решений для этого уровня — сначала пройди его (F или руками)'); return; } if (!best) { alert('Нет сохранённых решений для этого уровня — сначала пройди его (F или руками)'); return; }
startFlatPlayback(best.moves); startFlatPlayback(best.moves);
} }
@@ -2080,6 +2084,7 @@ window.addEventListener('keydown', e => {
if (key === 'p') { currentLevel=(currentLevel-1+LEVELS.length)%LEVELS.length; loadLevel(currentLevel); e.preventDefault(); return; } if (key === 'p') { currentLevel=(currentLevel-1+LEVELS.length)%LEVELS.length; loadLevel(currentLevel); e.preventDefault(); return; }
if (key === 'f') { startAutoSolve(); e.preventDefault(); return; } if (key === 'f') { startAutoSolve(); e.preventDefault(); return; }
if (key === 'g') { playBestSolution(); e.preventDefault(); return; } if (key === 'g') { playBestSolution(); e.preventDefault(); return; }
if (key === 'm') { backToMenu(); e.preventDefault(); return; }
// Стрелки и WASD // Стрелки и WASD
if (['arrowup','arrowdown','arrowleft','arrowright','w','a','s','d'].includes(key)) { if (['arrowup','arrowdown','arrowleft','arrowright','w','a','s','d'].includes(key)) {
@@ -2149,14 +2154,31 @@ document.getElementById('start-begin').addEventListener('click', () => {
}); });
document.getElementById('end-menu').addEventListener('click', () => showScreen('screen-menu')); document.getElementById('end-menu').addEventListener('click', () => showScreen('screen-menu'));
// Выход в меню из игры — гасим поиск/автопроигрывание, если шли, иначе
// они продолжат тикать в фоне за скрытым экраном.
function backToMenu() {
if (solveState) cancelAutoSolve();
autoSolveActive = false;
autoPlayMoves = null;
showScreen('screen-menu');
}
document.getElementById('menu-btn').addEventListener('click', backToMenu);
document.getElementById('settings-save').addEventListener('click', async () => { document.getElementById('settings-save').addEventListener('click', async () => {
const name = document.getElementById('settings-name-input').value.trim(); const name = document.getElementById('settings-name-input').value.trim();
if (!name) return; if (!name) return;
const btn = document.getElementById('settings-save');
btn.disabled = true;
const originalText = btn.textContent;
btn.textContent = 'Сохраняю…';
try { try {
await apiPost('set_name', { name }); await apiPost('set_name', { name });
showScreen(settingsReturnTo === null ? 'screen-menu' : settingsReturnTo); showScreen(settingsReturnTo === null ? 'screen-menu' : settingsReturnTo);
} catch { } catch {
alert('Не удалось сохранить имя — сервер недоступен'); alert('Не удалось сохранить имя — сервер недоступен');
} finally {
btn.disabled = false;
btn.textContent = originalText;
} }
}); });
document.getElementById('settings-name-input').addEventListener('keydown', e => { document.getElementById('settings-name-input').addEventListener('keydown', e => {