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
|
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
@@ -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)
|
||||||
|
|||||||
@@ -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
@@ -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();
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user