Merge branch '12-fortschritt-abspeichern' into dev
This commit is contained in:
+3
-21
@@ -1,29 +1,12 @@
|
|||||||
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 sqlalchemy import inspect, text
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
|
|
||||||
db = SQLAlchemy()
|
db = SQLAlchemy()
|
||||||
login_manager = LoginManager()
|
login_manager = LoginManager()
|
||||||
|
|
||||||
|
|
||||||
def apply_startup_migrations():
|
|
||||||
inspector = inspect(db.engine)
|
|
||||||
|
|
||||||
if "users" in inspector.get_table_names():
|
|
||||||
existing_columns = {column["name"] for column in inspector.get_columns("users")}
|
|
||||||
user_column_migrations = {
|
|
||||||
"two_factor_enabled": "ALTER TABLE users ADD COLUMN two_factor_enabled BOOLEAN NOT NULL DEFAULT 0",
|
|
||||||
"two_factor_secret": "ALTER TABLE users ADD COLUMN two_factor_secret VARCHAR(64)",
|
|
||||||
}
|
|
||||||
|
|
||||||
for column_name, statement in user_column_migrations.items():
|
|
||||||
if column_name not in existing_columns:
|
|
||||||
db.session.execute(text(statement))
|
|
||||||
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
def create_app():
|
def create_app():
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|
||||||
@@ -47,7 +30,6 @@ def create_app():
|
|||||||
|
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
db.create_all()
|
db.create_all()
|
||||||
apply_startup_migrations()
|
|
||||||
|
|
||||||
@login_manager.user_loader
|
@login_manager.user_loader
|
||||||
def load_user(user_id):
|
def load_user(user_id):
|
||||||
|
|||||||
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 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)
|
||||||
|
|||||||
@@ -94,6 +94,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;
|
||||||
@@ -141,6 +147,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;
|
||||||
}
|
}
|
||||||
@@ -157,6 +178,10 @@ body {
|
|||||||
/* 🎛️ Buttons */
|
/* 🎛️ Buttons */
|
||||||
.controls {
|
.controls {
|
||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn {
|
.btn {
|
||||||
@@ -173,6 +198,12 @@ body {
|
|||||||
background: #2563eb;
|
background: #2563eb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn:disabled,
|
||||||
|
.btn:disabled:hover {
|
||||||
|
background: #94a3b8;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
.btn.secondary {
|
.btn.secondary {
|
||||||
background: #64748b;
|
background: #64748b;
|
||||||
}
|
}
|
||||||
@@ -213,6 +244,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;
|
||||||
@@ -225,10 +329,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -394,6 +502,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;
|
||||||
@@ -403,7 +525,19 @@ body {
|
|||||||
padding: 15px 18px;
|
padding: 15px 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.nav-logout-form {
|
.nav-logout-form {
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.solver-panel-header,
|
||||||
|
.solver-speed {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
display: grid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.solver-step-info,
|
||||||
|
.solver-speed-value {
|
||||||
|
justify-self: start;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+408
-22
@@ -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();
|
||||||
|
|||||||
@@ -66,8 +66,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>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user