added step wise solution and an animation.
This commit is contained in:
Binary file not shown.
+3
-2
@@ -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()
|
||||
|
||||
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
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+409
-23
@@ -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");
|
||||
drawBoard();
|
||||
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;
|
||||
}
|
||||
|
||||
function resetBoard() {
|
||||
setSolverMessage(step.message || DEFAULT_SOLVER_MESSAGE);
|
||||
drawBoard();
|
||||
updateSolverControls();
|
||||
}
|
||||
|
||||
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);
|
||||
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();
|
||||
|
||||
@@ -61,8 +61,31 @@
|
||||
<p id="statusMessage"></p>
|
||||
<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">
|
||||
<button class="btn" onclick="solve()">Solve</button>
|
||||
<button class="btn" onclick="checkSolution()">Check</button>
|
||||
<button class="btn secondary" onclick="resetBoard()">Reset</button>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user