added step wise solution and an animation.

This commit is contained in:
2026-04-01 11:54:40 +02:00
parent b90d6d0f03
commit 02cb1d0a64
14 changed files with 689 additions and 27 deletions
Binary file not shown.
+3 -2
View File
@@ -1,7 +1,8 @@
from flask import Flask
import os import os
from flask_sqlalchemy import SQLAlchemy
from flask import Flask
from flask_login import LoginManager from flask_login import LoginManager
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy() db = SQLAlchemy()
login_manager = LoginManager() login_manager = LoginManager()
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+120 -1
View File
@@ -1,5 +1,6 @@
import json import json
import random import random
from datetime import datetime
from flask import Blueprint, jsonify, render_template, request from flask import Blueprint, jsonify, render_template, request
from flask_login import current_user, login_required 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"} 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): def solve_queens(board_size):
queen_positions = [-1] * board_size queen_positions = [-1] * board_size
queen_solutions = [] queen_solutions = []
@@ -64,6 +69,105 @@ def solve_rooks(board_size):
return rook_positions 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): def serialize_game_state(game_state):
board = json.loads(game_state.board) board = json.loads(game_state.board)
@@ -149,12 +253,24 @@ def solve_queens_route():
return jsonify(solve_queens(board_size)) 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") @main_bp.route("/solve_rooks")
def solve_rooks_route(): def solve_rooks_route():
board_size = parse_board_size(request.args.get("size")) board_size = parse_board_size(request.args.get("size"))
return jsonify(solve_rooks(board_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"]) @main_bp.route("/save", methods=["POST"])
@login_required @login_required
def save_game(): def save_game():
@@ -166,6 +282,7 @@ def save_game():
save_name = (data.get("saveName") or "").strip() or f"{piece_mode.title()} Save" save_name = (data.get("saveName") or "").strip() or f"{piece_mode.title()} Save"
save_note = (data.get("saveNote") or "").strip() save_note = (data.get("saveNote") or "").strip()
is_favorite = bool(data.get("isFavorite")) is_favorite = bool(data.get("isFavorite"))
timestamp = datetime.utcnow()
game_state = GameState( game_state = GameState(
user_id=current_user.id, user_id=current_user.id,
@@ -174,7 +291,9 @@ def save_game():
board_size=board_size, board_size=board_size,
save_name=save_name, save_name=save_name,
save_note=save_note, save_note=save_note,
is_favorite=is_favorite is_favorite=is_favorite,
created_at=timestamp,
updated_at=timestamp
) )
db.session.add(game_state) db.session.add(game_state)
+133
View File
@@ -44,6 +44,12 @@ body {
text-align: center; text-align: center;
} }
#statusMessage {
min-height: 24px;
margin: 0 0 8px;
font-weight: bold;
}
.board { .board {
overflow-x: auto; overflow-x: auto;
padding-bottom: 8px; padding-bottom: 8px;
@@ -91,6 +97,21 @@ body {
transform: scale(1.05); 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 { .white {
background: #e2e8f0; background: #e2e8f0;
} }
@@ -107,6 +128,10 @@ body {
/* 🎛️ Buttons */ /* 🎛️ Buttons */
.controls { .controls {
margin-top: 15px; margin-top: 15px;
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 10px;
} }
.btn { .btn {
@@ -123,6 +148,12 @@ body {
background: #2563eb; background: #2563eb;
} }
.btn:disabled,
.btn:disabled:hover {
background: #94a3b8;
cursor: not-allowed;
}
.btn.secondary { .btn.secondary {
background: #64748b; background: #64748b;
} }
@@ -163,6 +194,79 @@ body {
background: #22c55e; 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 { .board-size-info {
margin: 0 0 10px; margin: 0 0 10px;
color: #475569; color: #475569;
@@ -175,10 +279,14 @@ body {
width: min(320px, 85vw); width: min(320px, 85vw);
height: 100vh; height: 100vh;
padding: 24px; padding: 24px;
box-sizing: border-box;
background: white; background: white;
box-shadow: -10px 0 24px rgba(15, 23, 42, 0.18); box-shadow: -10px 0 24px rgba(15, 23, 42, 0.18);
transform: translateX(100%); transform: translateX(100%);
transition: transform 0.25s ease; transition: transform 0.25s ease;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
overscroll-behavior: contain;
z-index: 30; z-index: 30;
} }
@@ -329,6 +437,20 @@ body {
pointer-events: auto; pointer-events: auto;
} }
@keyframes solverPulse {
0% {
transform: scale(0.97);
}
50% {
transform: scale(1.03);
}
100% {
transform: scale(1);
}
}
@media (max-width: 720px) { @media (max-width: 720px) {
.container { .container {
padding: 12px; padding: 12px;
@@ -337,4 +459,15 @@ body {
.navbar { .navbar {
padding: 15px 18px; padding: 15px 18px;
} }
.solver-panel-header,
.solver-speed {
grid-template-columns: 1fr;
display: grid;
}
.solver-step-info,
.solver-speed-value {
justify-self: start;
}
} }
+408 -22
View File
@@ -2,17 +2,34 @@ const DEFAULT_BOARD_SIZE = 8;
const MIN_BOARD_SIZE = 4; const MIN_BOARD_SIZE = 4;
const MAX_BOARD_SIZE = 13; const MAX_BOARD_SIZE = 13;
const EMPTY_CELL = -1; const EMPTY_CELL = -1;
const DEFAULT_SOLVER_MESSAGE = "Bereite einen Ablauf vor und spiele ihn Schritt fuer Schritt ab.";
let mode = "queens"; let mode = "queens";
let boardSize = DEFAULT_BOARD_SIZE; let boardSize = DEFAULT_BOARD_SIZE;
let pendingBoardSize = DEFAULT_BOARD_SIZE; let pendingBoardSize = DEFAULT_BOARD_SIZE;
let board = createEmptyBoard(boardSize); 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 boardDiv = document.getElementById("board");
const statusMessage = document.getElementById("statusMessage"); const statusMessage = document.getElementById("statusMessage");
const queenList = document.getElementById("queenList"); const queenList = document.getElementById("queenList");
const btnQueens = document.getElementById("btnQueens"); const btnQueens = document.getElementById("btnQueens");
const btnRooks = document.getElementById("btnRooks"); 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 settingsSidebar = document.getElementById("settingsSidebar");
const sidebarBackdrop = document.getElementById("sidebarBackdrop"); const sidebarBackdrop = document.getElementById("sidebarBackdrop");
const saveSidebar = document.getElementById("saveSidebar"); const saveSidebar = document.getElementById("saveSidebar");
@@ -63,6 +80,21 @@ function setStatus(message = "", color = "") {
statusMessage.style.color = 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() { function updateBoardLayout() {
const cellSize = Math.max(32, Math.min(60, Math.floor(480 / boardSize))); 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`); 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) { function setMode(newMode) {
if (mode === newMode) {
return;
}
mode = newMode; mode = newMode;
resetBoard();
updateModeButtons(); updateModeButtons();
resetBoard({
statusMessageText: `${getPieceLabel(mode)}-Modus aktiviert.`,
statusColor: "#2563eb",
solverMessageText: `${getPieceLabel(mode)}-Solver bereit. ${DEFAULT_SOLVER_MESSAGE}`
});
} }
function closeAllPanels() { function closeAllPanels() {
@@ -99,8 +206,6 @@ function getConflicts() {
const colB = board[rowB]; const colB = board[rowB];
const sameColumn = colA === colB; const sameColumn = colA === colB;
const sameDiagonal = Math.abs(colA - colB) === Math.abs(rowA - rowB); 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; const hasConflict = mode === "rooks" ? sameColumn : sameColumn || sameDiagonal;
if (hasConflict) { if (hasConflict) {
@@ -116,16 +221,16 @@ function checkSolution() {
const allPlaced = board.every((col) => col !== EMPTY_CELL); const allPlaced = board.every((col) => col !== EMPTY_CELL);
if (!allPlaced) { if (!allPlaced) {
setStatus("❗ Noch nicht alle Figuren platziert.", "orange"); setStatus("Noch nicht alle Figuren platziert.", "#d97706");
return; return;
} }
if (getConflicts().length > 0) { if (getConflicts().length > 0) {
setStatus("❌ Es gibt Konflikte!", "red"); setStatus("Es gibt Konflikte auf dem Brett.", "red");
return; return;
} }
setStatus("🎉 Perfekt geloest!", "green"); setStatus("Perfekt geloest.", "green");
} }
function drawBoard() { function drawBoard() {
@@ -134,6 +239,7 @@ function drawBoard() {
updateBoardLayout(); updateBoardLayout();
const conflicts = getConflicts(); const conflicts = getConflicts();
const highlight = currentSolverHighlight;
for (let row = -1; row < boardSize; row++) { for (let row = -1; row < boardSize; row++) {
for (let col = -1; col < boardSize; col++) { for (let col = -1; col < boardSize; col++) {
@@ -150,10 +256,19 @@ function drawBoard() {
} else { } else {
cell.classList.add("cell", (row + col) % 2 === 0 ? "white" : "black"); 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) { if (board[row] === col) {
cell.textContent = mode === "queens" ? "♛" : "♜"; cell.textContent = mode === "queens" ? "♛" : "♜";
// Highlight pieces that are part of at least one conflict pair.
if (conflicts.some(([conflictRow, conflictCol]) => conflictRow === row && conflictCol === col)) { if (conflicts.some(([conflictRow, conflictCol]) => conflictRow === row && conflictCol === col)) {
cell.classList.add("invalid"); cell.classList.add("invalid");
} }
@@ -170,6 +285,18 @@ function drawBoard() {
} }
function placeQueen(row, col) { 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; board[row] = board[row] === col ? EMPTY_CELL : col;
drawBoard(); drawBoard();
} }
@@ -182,7 +309,9 @@ function previewBoardSize(size) {
function applyBoardSize() { function applyBoardSize() {
boardSize = pendingBoardSize; boardSize = pendingBoardSize;
discardSolverTrace(`${getPieceLabel(mode)}-Solver bereit. ${DEFAULT_SOLVER_MESSAGE}`);
board = createEmptyBoard(boardSize); board = createEmptyBoard(boardSize);
currentSolverHighlight = null;
updateBoardSizeDisplays(boardSize); updateBoardSizeDisplays(boardSize);
setStatus(`Brettgroesse auf ${boardSize} x ${boardSize} gesetzt.`, "#2563eb"); setStatus(`Brettgroesse auf ${boardSize} x ${boardSize} gesetzt.`, "#2563eb");
drawBoard(); drawBoard();
@@ -358,30 +487,284 @@ async function openSavePointsPanel() {
} }
function applyLoadedGame(data) { 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; boardSize = Array.isArray(data.board) ? clampBoardSize(data.board.length) : DEFAULT_BOARD_SIZE;
pendingBoardSize = boardSize; pendingBoardSize = boardSize;
mode = data.mode; mode = data.mode === "rooks" ? "rooks" : "queens";
currentSolverHighlight = null;
updateModeButtons(); updateModeButtons();
updateBoardSizeDisplays(boardSize); updateBoardSizeDisplays(boardSize);
drawBoard(); drawBoard();
} }
async function solve() { function applySolverStep(step) {
const endpoint = mode === "rooks" ? "/solve_rooks" : "/solve"; board = Array.isArray(step.board) ? step.board.slice() : createEmptyBoard(boardSize);
const response = await fetch(`${endpoint}?size=${boardSize}`);
const solution = await response.json();
// The queens endpoint may return multiple solutions; we display the first one. if (Number.isInteger(step.row) && Number.isInteger(step.col)) {
board = mode === "queens" && Array.isArray(solution[0]) ? solution[0] : solution; currentSolverHighlight = {
setStatus(`Loesung fuer ${boardSize} x ${boardSize} geladen.`, "green"); 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(); 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); board = createEmptyBoard(boardSize);
currentSolverHighlight = null;
if (statusMessageText) {
setStatus(statusMessageText, statusColor);
} else {
setStatus(); setStatus();
}
drawBoard(); drawBoard();
} }
@@ -401,7 +784,7 @@ function updateQueenList() {
li.textContent = `${mode === "queens" ? "Dame" : "Turm"} ${count}: ${position}`; li.textContent = `${mode === "queens" ? "Dame" : "Turm"} ${count}: ${position}`;
queenList.appendChild(li); queenList.appendChild(li);
count++; count += 1;
} }
} }
@@ -422,7 +805,7 @@ async function saveGame() {
}); });
if (!response.ok) { if (!response.ok) {
setStatus("❌ Der Speicherstand konnte nicht gespeichert werden.", "red"); setStatus("Der Speicherstand konnte nicht gespeichert werden.", "red");
return; return;
} }
@@ -443,7 +826,7 @@ async function loadSavePoint(savePointId) {
const response = await fetch(`/load/${savePointId}`); const response = await fetch(`/load/${savePointId}`);
if (!response.ok) { if (!response.ok) {
setStatus("❌ Dieser Speicherstand konnte nicht geladen werden.", "red"); setStatus("Dieser Speicherstand konnte nicht geladen werden.", "red");
return; return;
} }
@@ -460,7 +843,7 @@ async function toggleSavePointFavorite(savePointId) {
}); });
if (!response.ok) { if (!response.ok) {
setStatus("❌ Favorit konnte nicht aktualisiert werden.", "red"); setStatus("Favorit konnte nicht aktualisiert werden.", "red");
return; return;
} }
@@ -480,7 +863,7 @@ async function deleteSavePoint(savePointId) {
}); });
if (!response.ok) { if (!response.ok) {
setStatus("❌ Speicherpunkt konnte nicht geloescht werden.", "red"); setStatus("Speicherpunkt konnte nicht geloescht werden.", "red");
return; return;
} }
@@ -491,4 +874,7 @@ async function deleteSavePoint(savePointId) {
pendingBoardSize = boardSize; pendingBoardSize = boardSize;
updateModeButtons(); updateModeButtons();
updateBoardSizeDisplays(boardSize); updateBoardSizeDisplays(boardSize);
updateSolverSpeed(solverSpeedMs);
setSolverMessage(`${getPieceLabel(mode)}-Solver bereit. ${DEFAULT_SOLVER_MESSAGE}`);
updateSolverControls();
drawBoard(); drawBoard();
+24 -1
View File
@@ -61,8 +61,31 @@
<p id="statusMessage"></p> <p id="statusMessage"></p>
<div class="board" id="board"></div> <div class="board" id="board"></div>
<div class="solver-panel">
<div class="solver-panel-header">
<div>
<h3>Solver-Ablauf</h3>
<p class="solver-message" id="solverMessage">Bereite einen Ablauf vor und spiele ihn Schritt fuer Schritt ab.</p>
</div>
<p class="solver-step-info" id="solverStepInfo">Schritt 0 / 0</p>
</div>
<div class="solver-controls">
<button class="btn" onclick="prepareSolutionTrace()" id="btnPrepareSolve" type="button">Solve Step-by-Step</button>
<button class="btn secondary" onclick="instantSolve()" id="btnInstantSolve" type="button">Instant Solve</button>
<button class="btn secondary" onclick="nextSolverStep()" id="btnNextStep" type="button">Next Step</button>
<button class="btn secondary" onclick="toggleSolverPlayback()" id="btnPlayPause" type="button">Play Animation</button>
</div>
<div class="solver-speed">
<label class="solver-speed-label" for="solverSpeedInput">Speed</label>
<input id="solverSpeedInput" type="range" min="80" max="900" step="20" value="320"
oninput="updateSolverSpeed(this.value)">
<span class="solver-speed-value" id="solverSpeedValue">320 ms</span>
</div>
</div>
<div class="controls"> <div class="controls">
<button class="btn" onclick="solve()">Solve</button>
<button class="btn" onclick="checkSolution()">Check</button> <button class="btn" onclick="checkSolution()">Check</button>
<button class="btn secondary" onclick="resetBoard()">Reset</button> <button class="btn secondary" onclick="resetBoard()">Reset</button>