diff --git a/__pycache__/run.cpython-312.pyc b/__pycache__/run.cpython-312.pyc new file mode 100644 index 0000000..b970ad6 Binary files /dev/null and b/__pycache__/run.cpython-312.pyc differ diff --git a/app/__init__.py b/app/__init__.py index ddb86b3..621c801 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,7 +1,8 @@ -from flask import Flask import os -from flask_sqlalchemy import SQLAlchemy + +from flask import Flask from flask_login import LoginManager +from flask_sqlalchemy import SQLAlchemy db = SQLAlchemy() login_manager = LoginManager() diff --git a/app/__pycache__/__init__.cpython-311.pyc b/app/__pycache__/__init__.cpython-311.pyc index c75a2cb..f13f5cf 100644 Binary files a/app/__pycache__/__init__.cpython-311.pyc and b/app/__pycache__/__init__.cpython-311.pyc differ diff --git a/app/__pycache__/__init__.cpython-312.pyc b/app/__pycache__/__init__.cpython-312.pyc index ae97128..e7768a0 100644 Binary files a/app/__pycache__/__init__.cpython-312.pyc and b/app/__pycache__/__init__.cpython-312.pyc differ diff --git a/app/models/__pycache__/gamestate.cpython-311.pyc b/app/models/__pycache__/gamestate.cpython-311.pyc index 9a1f79a..15f2556 100644 Binary files a/app/models/__pycache__/gamestate.cpython-311.pyc and b/app/models/__pycache__/gamestate.cpython-311.pyc differ diff --git a/app/models/__pycache__/gamestate.cpython-312.pyc b/app/models/__pycache__/gamestate.cpython-312.pyc index db13518..7606bb2 100644 Binary files a/app/models/__pycache__/gamestate.cpython-312.pyc and b/app/models/__pycache__/gamestate.cpython-312.pyc differ diff --git a/app/models/__pycache__/user.cpython-311.pyc b/app/models/__pycache__/user.cpython-311.pyc index 0748aa0..1ecb09a 100644 Binary files a/app/models/__pycache__/user.cpython-311.pyc and b/app/models/__pycache__/user.cpython-311.pyc differ diff --git a/app/routes/__pycache__/login_routes.cpython-311.pyc b/app/routes/__pycache__/login_routes.cpython-311.pyc index 60142fb..9f461fa 100644 Binary files a/app/routes/__pycache__/login_routes.cpython-311.pyc and b/app/routes/__pycache__/login_routes.cpython-311.pyc differ diff --git a/app/routes/__pycache__/main_routes.cpython-311.pyc b/app/routes/__pycache__/main_routes.cpython-311.pyc index ec65ad5..7e04893 100644 Binary files a/app/routes/__pycache__/main_routes.cpython-311.pyc and b/app/routes/__pycache__/main_routes.cpython-311.pyc differ diff --git a/app/routes/__pycache__/main_routes.cpython-312.pyc b/app/routes/__pycache__/main_routes.cpython-312.pyc index 237fe6e..ab7d6ab 100644 Binary files a/app/routes/__pycache__/main_routes.cpython-312.pyc and b/app/routes/__pycache__/main_routes.cpython-312.pyc differ diff --git a/app/routes/main_routes.py b/app/routes/main_routes.py index c1b77b2..15ea363 100644 --- a/app/routes/main_routes.py +++ b/app/routes/main_routes.py @@ -1,5 +1,6 @@ import json import random +from datetime import datetime from flask import Blueprint, jsonify, render_template, request from flask_login import current_user, login_required @@ -27,6 +28,10 @@ def parse_favorite_flag(raw_value): return str(raw_value).lower() in {"1", "true", "yes", "on"} +def get_position_label(row, col, board_size): + return f"{chr(65 + col)}{board_size - row}" + + def solve_queens(board_size): queen_positions = [-1] * board_size queen_solutions = [] @@ -64,6 +69,105 @@ def solve_rooks(board_size): return rook_positions +def build_queens_trace(board_size): + queen_positions = [-1] * board_size + steps = [] + + def is_queen_position_safe(row, col): + for previous_row in range(row): + previous_col = queen_positions[previous_row] + + if previous_col == col: + return False + + if abs(previous_col - col) == abs(previous_row - row): + return False + + return True + + def place_queen(row): + if row == board_size: + steps.append({ + "type": "solution", + "row": None, + "col": None, + "board": queen_positions[:], + "message": f"Loesung gefunden. Alle {board_size} Damen stehen ohne Konflikte." + }) + return True + + for col in range(board_size): + if not is_queen_position_safe(row, col): + continue + + queen_positions[row] = col + position = get_position_label(row, col, board_size) + steps.append({ + "type": "place", + "row": row, + "col": col, + "board": queen_positions[:], + "message": f"Dame auf {position} gesetzt." + }) + + if place_queen(row + 1): + return True + + queen_positions[row] = -1 + steps.append({ + "type": "remove", + "row": row, + "col": col, + "board": queen_positions[:], + "message": f"Backtracking: Dame von {position} entfernt." + }) + + return False + + solved = place_queen(0) + + return { + "mode": "queens", + "board_size": board_size, + "initial_board": [-1] * board_size, + "steps": steps, + "solved": solved + } + + +def build_rooks_trace(board_size): + rook_positions = solve_rooks(board_size) + board = [-1] * board_size + steps = [] + + for row, col in enumerate(rook_positions): + board[row] = col + position = get_position_label(row, col, board_size) + steps.append({ + "type": "place", + "row": row, + "col": col, + "board": board[:], + "message": f"Turm auf {position} gesetzt." + }) + + steps.append({ + "type": "solution", + "row": None, + "col": None, + "board": board[:], + "message": f"Loesung gefunden. Alle {board_size} Tuerme stehen ohne Konflikte." + }) + + return { + "mode": "rooks", + "board_size": board_size, + "initial_board": [-1] * board_size, + "steps": steps, + "solved": True + } + + def serialize_game_state(game_state): board = json.loads(game_state.board) @@ -149,12 +253,24 @@ def solve_queens_route(): return jsonify(solve_queens(board_size)) +@main_bp.route("/solve_trace") +def solve_queens_trace_route(): + board_size = parse_board_size(request.args.get("size")) + return jsonify(build_queens_trace(board_size)) + + @main_bp.route("/solve_rooks") def solve_rooks_route(): board_size = parse_board_size(request.args.get("size")) return jsonify(solve_rooks(board_size)) +@main_bp.route("/solve_rooks_trace") +def solve_rooks_trace_route(): + board_size = parse_board_size(request.args.get("size")) + return jsonify(build_rooks_trace(board_size)) + + @main_bp.route("/save", methods=["POST"]) @login_required def save_game(): @@ -166,6 +282,7 @@ def save_game(): save_name = (data.get("saveName") or "").strip() or f"{piece_mode.title()} Save" save_note = (data.get("saveNote") or "").strip() is_favorite = bool(data.get("isFavorite")) + timestamp = datetime.utcnow() game_state = GameState( user_id=current_user.id, @@ -174,7 +291,9 @@ def save_game(): board_size=board_size, save_name=save_name, save_note=save_note, - is_favorite=is_favorite + is_favorite=is_favorite, + created_at=timestamp, + updated_at=timestamp ) db.session.add(game_state) diff --git a/app/static/css/dashboard.css b/app/static/css/dashboard.css index c3ca90f..bf902fd 100644 --- a/app/static/css/dashboard.css +++ b/app/static/css/dashboard.css @@ -44,6 +44,12 @@ body { text-align: center; } +#statusMessage { + min-height: 24px; + margin: 0 0 8px; + font-weight: bold; +} + .board { overflow-x: auto; padding-bottom: 8px; @@ -91,6 +97,21 @@ body { transform: scale(1.05); } +.cell.solver-place { + box-shadow: inset 0 0 0 4px #22c55e; + animation: solverPulse 0.35s ease; +} + +.cell.solver-remove { + box-shadow: inset 0 0 0 4px #f59e0b; + animation: solverPulse 0.35s ease; +} + +.cell.solver-solution { + box-shadow: inset 0 0 0 4px #2563eb; + animation: solverPulse 0.35s ease; +} + .white { background: #e2e8f0; } @@ -107,6 +128,10 @@ body { /* 🎛️ Buttons */ .controls { margin-top: 15px; + display: flex; + justify-content: center; + flex-wrap: wrap; + gap: 10px; } .btn { @@ -123,6 +148,12 @@ body { background: #2563eb; } +.btn:disabled, +.btn:disabled:hover { + background: #94a3b8; + cursor: not-allowed; +} + .btn.secondary { background: #64748b; } @@ -163,6 +194,79 @@ body { background: #22c55e; } +.solver-panel { + margin-top: 12px; + padding: 16px; + border-radius: 12px; + border: 1px solid #dbe4ee; + background: #f8fafc; + text-align: left; +} + +.solver-panel-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + gap: 16px; +} + +.solver-panel h3 { + margin: 0 0 6px; + color: #1e293b; +} + +.solver-message { + margin: 0; + color: #475569; +} + +.solver-step-info { + margin: 0; + padding: 6px 10px; + border-radius: 999px; + background: #dbeafe; + color: #1d4ed8; + font-size: 14px; + font-weight: bold; + white-space: nowrap; +} + +.solver-controls { + display: flex; + flex-wrap: wrap; + gap: 10px; + margin-top: 14px; +} + +.solver-controls .btn, +.controls .btn { + margin: 0; +} + +.solver-speed { + display: grid; + grid-template-columns: auto 1fr auto; + align-items: center; + gap: 12px; + margin-top: 14px; +} + +.solver-speed input[type="range"] { + width: 100%; +} + +.solver-speed-label { + font-weight: bold; + color: #334155; +} + +.solver-speed-value { + min-width: 62px; + text-align: right; + color: #475569; + font-variant-numeric: tabular-nums; +} + .board-size-info { margin: 0 0 10px; color: #475569; @@ -175,10 +279,14 @@ body { width: min(320px, 85vw); height: 100vh; padding: 24px; + box-sizing: border-box; background: white; box-shadow: -10px 0 24px rgba(15, 23, 42, 0.18); transform: translateX(100%); transition: transform 0.25s ease; + overflow-y: auto; + -webkit-overflow-scrolling: touch; + overscroll-behavior: contain; z-index: 30; } @@ -329,6 +437,20 @@ body { pointer-events: auto; } +@keyframes solverPulse { + 0% { + transform: scale(0.97); + } + + 50% { + transform: scale(1.03); + } + + 100% { + transform: scale(1); + } +} + @media (max-width: 720px) { .container { padding: 12px; @@ -337,4 +459,15 @@ body { .navbar { padding: 15px 18px; } + + .solver-panel-header, + .solver-speed { + grid-template-columns: 1fr; + display: grid; + } + + .solver-step-info, + .solver-speed-value { + justify-self: start; + } } diff --git a/app/static/js/dashboard.js b/app/static/js/dashboard.js index 437d712..ca8b30c 100644 --- a/app/static/js/dashboard.js +++ b/app/static/js/dashboard.js @@ -2,17 +2,34 @@ const DEFAULT_BOARD_SIZE = 8; const MIN_BOARD_SIZE = 4; const MAX_BOARD_SIZE = 13; const EMPTY_CELL = -1; +const DEFAULT_SOLVER_MESSAGE = "Bereite einen Ablauf vor und spiele ihn Schritt fuer Schritt ab."; let mode = "queens"; let boardSize = DEFAULT_BOARD_SIZE; let pendingBoardSize = DEFAULT_BOARD_SIZE; let board = createEmptyBoard(boardSize); +let solverSteps = []; +let solverStepIndex = -1; +let solverTimerId = null; +let solverSpeedMs = 320; +let solverLoading = false; +let solverRequestId = 0; +let currentSolverHighlight = null; + const boardDiv = document.getElementById("board"); const statusMessage = document.getElementById("statusMessage"); const queenList = document.getElementById("queenList"); const btnQueens = document.getElementById("btnQueens"); const btnRooks = document.getElementById("btnRooks"); +const btnPrepareSolve = document.getElementById("btnPrepareSolve"); +const btnInstantSolve = document.getElementById("btnInstantSolve"); +const btnNextStep = document.getElementById("btnNextStep"); +const btnPlayPause = document.getElementById("btnPlayPause"); +const solverMessage = document.getElementById("solverMessage"); +const solverStepInfo = document.getElementById("solverStepInfo"); +const solverSpeedInput = document.getElementById("solverSpeedInput"); +const solverSpeedValue = document.getElementById("solverSpeedValue"); const settingsSidebar = document.getElementById("settingsSidebar"); const sidebarBackdrop = document.getElementById("sidebarBackdrop"); const saveSidebar = document.getElementById("saveSidebar"); @@ -63,6 +80,21 @@ function setStatus(message = "", color = "") { statusMessage.style.color = color; } +function setSolverMessage(message = DEFAULT_SOLVER_MESSAGE) { + solverMessage.textContent = message; +} + +function updateSolverSpeed(rawValue) { + solverSpeedMs = Number(rawValue); + solverSpeedInput.value = String(solverSpeedMs); + solverSpeedValue.textContent = `${solverSpeedMs} ms`; + + if (isSolverPlaying()) { + stopSolverPlayback(false); + scheduleNextSolverPlaybackStep(); + } +} + function updateBoardLayout() { const cellSize = Math.max(32, Math.min(60, Math.floor(480 / boardSize))); @@ -70,10 +102,85 @@ function updateBoardLayout() { boardDiv.style.setProperty("--cell-size", `${cellSize}px`); } +function isSolverPlaying() { + return solverTimerId !== null; +} + +function hasPreparedSolverTrace() { + return solverSteps.length > 0; +} + +function hasCompletedSolverTrace() { + return hasPreparedSolverTrace() && solverStepIndex >= solverSteps.length - 1; +} + +function stopSolverPlayback(refreshControls = true) { + if (solverTimerId !== null) { + window.clearTimeout(solverTimerId); + solverTimerId = null; + } + + if (refreshControls) { + updateSolverControls(); + } +} + +function updateSolverControls() { + const currentStep = Math.max(0, solverStepIndex + 1); + + btnPrepareSolve.disabled = solverLoading; + btnInstantSolve.disabled = solverLoading; + btnNextStep.disabled = solverLoading || hasCompletedSolverTrace(); + btnPlayPause.disabled = solverLoading; + + btnPrepareSolve.textContent = solverLoading ? "Preparing..." : "Solve Step-by-Step"; + + if (isSolverPlaying()) { + btnPlayPause.textContent = "Pause"; + } else if (hasCompletedSolverTrace()) { + btnPlayPause.textContent = "Replay Animation"; + } else { + btnPlayPause.textContent = "Play Animation"; + } + + solverStepInfo.textContent = `Schritt ${currentStep} / ${solverSteps.length}`; +} + +function discardSolverTrace(message = DEFAULT_SOLVER_MESSAGE) { + solverRequestId += 1; + solverLoading = false; + stopSolverPlayback(false); + + solverSteps = []; + solverStepIndex = -1; + currentSolverHighlight = null; + + setSolverMessage(message); + updateSolverControls(); +} + +function rewindSolverTrace() { + stopSolverPlayback(false); + solverStepIndex = -1; + currentSolverHighlight = null; + board = createEmptyBoard(boardSize); + setSolverMessage("Ablauf zurueckgesetzt. Starte die Animation erneut oder gehe weiter mit Next Step."); + drawBoard(); + updateSolverControls(); +} + function setMode(newMode) { + if (mode === newMode) { + return; + } + mode = newMode; - resetBoard(); updateModeButtons(); + resetBoard({ + statusMessageText: `${getPieceLabel(mode)}-Modus aktiviert.`, + statusColor: "#2563eb", + solverMessageText: `${getPieceLabel(mode)}-Solver bereit. ${DEFAULT_SOLVER_MESSAGE}` + }); } function closeAllPanels() { @@ -99,8 +206,6 @@ function getConflicts() { const colB = board[rowB]; const sameColumn = colA === colB; const sameDiagonal = Math.abs(colA - colB) === Math.abs(rowA - rowB); - - // Rooks only attack vertically, queens also attack diagonally. const hasConflict = mode === "rooks" ? sameColumn : sameColumn || sameDiagonal; if (hasConflict) { @@ -116,16 +221,16 @@ function checkSolution() { const allPlaced = board.every((col) => col !== EMPTY_CELL); if (!allPlaced) { - setStatus("❗ Noch nicht alle Figuren platziert.", "orange"); + setStatus("Noch nicht alle Figuren platziert.", "#d97706"); return; } if (getConflicts().length > 0) { - setStatus("❌ Es gibt Konflikte!", "red"); + setStatus("Es gibt Konflikte auf dem Brett.", "red"); return; } - setStatus("🎉 Perfekt geloest!", "green"); + setStatus("Perfekt geloest.", "green"); } function drawBoard() { @@ -134,6 +239,7 @@ function drawBoard() { updateBoardLayout(); const conflicts = getConflicts(); + const highlight = currentSolverHighlight; for (let row = -1; row < boardSize; row++) { for (let col = -1; col < boardSize; col++) { @@ -150,10 +256,19 @@ function drawBoard() { } else { cell.classList.add("cell", (row + col) % 2 === 0 ? "white" : "black"); + if (highlight && highlight.row === row && highlight.col === col) { + cell.classList.add( + highlight.type === "remove" + ? "solver-remove" + : highlight.type === "solution" + ? "solver-solution" + : "solver-place" + ); + } + if (board[row] === col) { cell.textContent = mode === "queens" ? "♛" : "♜"; - // Highlight pieces that are part of at least one conflict pair. if (conflicts.some(([conflictRow, conflictCol]) => conflictRow === row && conflictCol === col)) { cell.classList.add("invalid"); } @@ -170,6 +285,18 @@ function drawBoard() { } function placeQueen(row, col) { + if (solverLoading) { + return; + } + + if (hasPreparedSolverTrace() || isSolverPlaying()) { + discardSolverTrace("Solver-Ablauf verworfen, weil das Brett manuell geaendert wurde."); + setStatus("Solver-Ablauf verworfen. Bereite ihn bei Bedarf neu vor.", "#d97706"); + } else { + setStatus(); + } + + currentSolverHighlight = null; board[row] = board[row] === col ? EMPTY_CELL : col; drawBoard(); } @@ -182,7 +309,9 @@ function previewBoardSize(size) { function applyBoardSize() { boardSize = pendingBoardSize; + discardSolverTrace(`${getPieceLabel(mode)}-Solver bereit. ${DEFAULT_SOLVER_MESSAGE}`); board = createEmptyBoard(boardSize); + currentSolverHighlight = null; updateBoardSizeDisplays(boardSize); setStatus(`Brettgroesse auf ${boardSize} x ${boardSize} gesetzt.`, "#2563eb"); drawBoard(); @@ -358,30 +487,284 @@ async function openSavePointsPanel() { } function applyLoadedGame(data) { - board = data.board; + discardSolverTrace(`${getPieceLabel(data.mode || "queens")}-Solver bereit. ${DEFAULT_SOLVER_MESSAGE}`); + + board = Array.isArray(data.board) ? data.board.slice() : createEmptyBoard(DEFAULT_BOARD_SIZE); boardSize = Array.isArray(data.board) ? clampBoardSize(data.board.length) : DEFAULT_BOARD_SIZE; pendingBoardSize = boardSize; - mode = data.mode; + mode = data.mode === "rooks" ? "rooks" : "queens"; + currentSolverHighlight = null; updateModeButtons(); updateBoardSizeDisplays(boardSize); drawBoard(); } -async function solve() { - const endpoint = mode === "rooks" ? "/solve_rooks" : "/solve"; - const response = await fetch(`${endpoint}?size=${boardSize}`); - const solution = await response.json(); +function applySolverStep(step) { + board = Array.isArray(step.board) ? step.board.slice() : createEmptyBoard(boardSize); - // The queens endpoint may return multiple solutions; we display the first one. - board = mode === "queens" && Array.isArray(solution[0]) ? solution[0] : solution; - setStatus(`Loesung fuer ${boardSize} x ${boardSize} geladen.`, "green"); + if (Number.isInteger(step.row) && Number.isInteger(step.col)) { + currentSolverHighlight = { + row: step.row, + col: step.col, + type: step.type + }; + } else if (step.type === "solution" && solverStepIndex > 0) { + const previousStep = solverSteps[solverStepIndex - 1]; + currentSolverHighlight = Number.isInteger(previousStep.row) && Number.isInteger(previousStep.col) + ? { + row: previousStep.row, + col: previousStep.col, + type: "solution" + } + : null; + } else { + currentSolverHighlight = null; + } + + setSolverMessage(step.message || DEFAULT_SOLVER_MESSAGE); drawBoard(); + updateSolverControls(); } -function resetBoard() { +function advanceSolverStep() { + if (!hasPreparedSolverTrace() || hasCompletedSolverTrace()) { + stopSolverPlayback(false); + updateSolverControls(); + return false; + } + + solverStepIndex += 1; + applySolverStep(solverSteps[solverStepIndex]); + + if (hasCompletedSolverTrace()) { + stopSolverPlayback(false); + setStatus(`Loesung fuer ${boardSize} x ${boardSize} Schritt fuer Schritt aufgebaut.`, "green"); + updateSolverControls(); + } + + return true; +} + +function scheduleNextSolverPlaybackStep() { + stopSolverPlayback(false); + + solverTimerId = window.setTimeout(() => { + solverTimerId = null; + + const advanced = advanceSolverStep(); + + if (advanced && !hasCompletedSolverTrace()) { + scheduleNextSolverPlaybackStep(); + return; + } + + updateSolverControls(); + }, solverSpeedMs); + + updateSolverControls(); +} + +function startSolverPlayback(stepImmediately = true) { + if (!hasPreparedSolverTrace() || hasCompletedSolverTrace()) { + updateSolverControls(); + return; + } + + if (stepImmediately) { + const advanced = advanceSolverStep(); + + if (!advanced || hasCompletedSolverTrace()) { + updateSolverControls(); + return; + } + } + + scheduleNextSolverPlaybackStep(); +} + +async function prepareSolutionTrace() { + const endpoint = mode === "rooks" ? "/solve_rooks_trace" : "/solve_trace"; + const requestId = ++solverRequestId; + + solverLoading = true; + stopSolverPlayback(false); + solverSteps = []; + solverStepIndex = -1; + currentSolverHighlight = null; + setSolverMessage(`Berechne ${getPieceLabel(mode)}-Ablauf fuer ${boardSize} x ${boardSize}...`); + setStatus(`Solver berechnet die Schritte fuer ${boardSize} x ${boardSize}.`, "#2563eb"); + updateSolverControls(); + + try { + const response = await fetch(`${endpoint}?size=${boardSize}`); + + if (!response.ok) { + throw new Error("trace request failed"); + } + + const trace = await response.json(); + + if (requestId !== solverRequestId) { + return false; + } + + solverSteps = Array.isArray(trace.steps) ? trace.steps : []; + solverStepIndex = -1; + board = Array.isArray(trace.initial_board) ? trace.initial_board.slice() : createEmptyBoard(boardSize); + currentSolverHighlight = null; + + if (!trace.solved || solverSteps.length === 0) { + setSolverMessage("Keine Loesung gefunden."); + setStatus("Keine Loesung fuer dieses Brett gefunden.", "red"); + drawBoard(); + return false; + } + + setSolverMessage( + mode === "queens" + ? `Ablauf bereit mit ${solverSteps.length} Schritten. Nutze Next Step oder Play Animation, um das Backtracking zu verfolgen.` + : `Ablauf bereit mit ${solverSteps.length} Schritten. Nutze Next Step oder starte die Animation.` + ); + setStatus("Solver-Ablauf bereit. Nutze Next Step oder Play Animation.", "#2563eb"); + drawBoard(); + return true; + } catch (error) { + if (requestId !== solverRequestId) { + return false; + } + + setSolverMessage("Der Solver-Ablauf konnte nicht geladen werden."); + setStatus("Der Solver-Ablauf konnte nicht geladen werden.", "red"); + return false; + } finally { + if (requestId === solverRequestId) { + solverLoading = false; + updateSolverControls(); + } + } +} + +async function nextSolverStep() { + if (solverLoading) { + return; + } + + if (!hasPreparedSolverTrace()) { + const prepared = await prepareSolutionTrace(); + + if (!prepared) { + return; + } + } + + if (isSolverPlaying()) { + stopSolverPlayback(false); + } + + if (!advanceSolverStep()) { + setStatus("Der Solver-Ablauf ist bereits abgeschlossen.", "#2563eb"); + } +} + +async function toggleSolverPlayback() { + if (solverLoading) { + return; + } + + if (isSolverPlaying()) { + stopSolverPlayback(); + setStatus("Solver-Animation pausiert.", "#2563eb"); + return; + } + + if (!hasPreparedSolverTrace()) { + const prepared = await prepareSolutionTrace(); + + if (!prepared) { + return; + } + } + + if (hasCompletedSolverTrace()) { + rewindSolverTrace(); + } + + setStatus("Solver-Animation laeuft.", "#2563eb"); + startSolverPlayback(true); +} + +async function solve() { + await prepareSolutionTrace(); +} + +async function instantSolve() { + if (solverLoading) { + return; + } + + const endpoint = mode === "rooks" ? "/solve_rooks" : "/solve"; + const requestId = ++solverRequestId; + + solverLoading = true; + stopSolverPlayback(false); + solverSteps = []; + solverStepIndex = -1; + currentSolverHighlight = null; + setSolverMessage(`Lade sofortige ${getPieceLabel(mode)}-Loesung fuer ${boardSize} x ${boardSize}...`); + setStatus(`Sofortige Loesung wird geladen.`, "#2563eb"); + updateSolverControls(); + + try { + const response = await fetch(`${endpoint}?size=${boardSize}`); + + if (!response.ok) { + throw new Error("instant solve request failed"); + } + + const solution = await response.json(); + + if (requestId !== solverRequestId) { + return; + } + + board = mode === "queens" && Array.isArray(solution[0]) ? solution[0].slice() : solution.slice(); + currentSolverHighlight = null; + setSolverMessage("Sofortige Loesung geladen. Starte Solve Step-by-Step, wenn du den Ablauf sehen moechtest."); + setStatus(`Loesung fuer ${boardSize} x ${boardSize} sofort geladen.`, "green"); + drawBoard(); + } catch (error) { + if (requestId !== solverRequestId) { + return; + } + + setSolverMessage("Die sofortige Loesung konnte nicht geladen werden."); + setStatus("Die sofortige Loesung konnte nicht geladen werden.", "red"); + } finally { + if (requestId === solverRequestId) { + solverLoading = false; + updateSolverControls(); + } + } +} + +function resetBoard(options = {}) { + const { + statusMessageText = "", + statusColor = "", + solverMessageText = `${getPieceLabel(mode)}-Solver bereit. ${DEFAULT_SOLVER_MESSAGE}` + } = options; + + discardSolverTrace(solverMessageText); board = createEmptyBoard(boardSize); - setStatus(); + currentSolverHighlight = null; + + if (statusMessageText) { + setStatus(statusMessageText, statusColor); + } else { + setStatus(); + } + drawBoard(); } @@ -401,7 +784,7 @@ function updateQueenList() { li.textContent = `${mode === "queens" ? "Dame" : "Turm"} ${count}: ${position}`; queenList.appendChild(li); - count++; + count += 1; } } @@ -422,7 +805,7 @@ async function saveGame() { }); if (!response.ok) { - setStatus("❌ Der Speicherstand konnte nicht gespeichert werden.", "red"); + setStatus("Der Speicherstand konnte nicht gespeichert werden.", "red"); return; } @@ -443,7 +826,7 @@ async function loadSavePoint(savePointId) { const response = await fetch(`/load/${savePointId}`); if (!response.ok) { - setStatus("❌ Dieser Speicherstand konnte nicht geladen werden.", "red"); + setStatus("Dieser Speicherstand konnte nicht geladen werden.", "red"); return; } @@ -460,7 +843,7 @@ async function toggleSavePointFavorite(savePointId) { }); if (!response.ok) { - setStatus("❌ Favorit konnte nicht aktualisiert werden.", "red"); + setStatus("Favorit konnte nicht aktualisiert werden.", "red"); return; } @@ -480,7 +863,7 @@ async function deleteSavePoint(savePointId) { }); if (!response.ok) { - setStatus("❌ Speicherpunkt konnte nicht geloescht werden.", "red"); + setStatus("Speicherpunkt konnte nicht geloescht werden.", "red"); return; } @@ -491,4 +874,7 @@ async function deleteSavePoint(savePointId) { pendingBoardSize = boardSize; updateModeButtons(); updateBoardSizeDisplays(boardSize); +updateSolverSpeed(solverSpeedMs); +setSolverMessage(`${getPieceLabel(mode)}-Solver bereit. ${DEFAULT_SOLVER_MESSAGE}`); +updateSolverControls(); drawBoard(); diff --git a/app/templates/dashboard.html b/app/templates/dashboard.html index 1c82d34..72fddbd 100644 --- a/app/templates/dashboard.html +++ b/app/templates/dashboard.html @@ -61,8 +61,31 @@

+
+
+
+

Solver-Ablauf

+

Bereite einen Ablauf vor und spiele ihn Schritt fuer Schritt ab.

+
+

Schritt 0 / 0

+
+ +
+ + + + +
+ +
+ + + 320 ms +
+
+
-