7 Commits

18 changed files with 1706 additions and 171 deletions
Binary file not shown.
+4 -2
View File
@@ -1,7 +1,8 @@
from flask import Flask
import os import os
from flask_sqlalchemy import SQLAlchemy
from flask import Flask
from flask_login import LoginManager from flask_login import LoginManager
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy() db = SQLAlchemy()
login_manager = LoginManager() login_manager = LoginManager()
@@ -20,6 +21,7 @@ def create_app():
login_manager.login_view = "login.login" login_manager.login_view = "login.login"
from .models.user import User from .models.user import User
from .models.gamestate import GameState
with app.app_context(): with app.app_context():
db.create_all() db.create_all()
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+21
View File
@@ -0,0 +1,21 @@
from .. import db
class GameState(db.Model):
__tablename__ = "game_state"
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, nullable=False)
board = db.Column(db.Text, nullable=False)
mode = db.Column(db.String(10), nullable=False)
board_size = db.Column(db.Integer, nullable=False, default=8)
save_name = db.Column(db.String(80), nullable=False, default="Unnamed Save")
save_note = db.Column(db.Text, nullable=False, default="")
is_favorite = db.Column(db.Boolean, nullable=False, default=False)
created_at = db.Column(db.DateTime, nullable=False, server_default=db.func.now())
updated_at = db.Column(
db.DateTime,
nullable=False,
server_default=db.func.now(),
onupdate=db.func.now()
)
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -46,7 +46,7 @@ def login():
return render_template("login.html") return render_template("login.html")
@login_bp.route('/logout') @login_bp.route('/logout', methods=['POST'])
@login_required @login_required
def logout(): def logout():
logout_user() logout_user()
+334 -32
View File
@@ -1,16 +1,227 @@
import json
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 login_required, current_user from flask_login import current_user, login_required
from .. import db
from ..models.gamestate import GameState
DEFAULT_BOARD_SIZE = 8
MIN_BOARD_SIZE = 4
MAX_BOARD_SIZE = 13
main_bp = Blueprint("main", __name__) main_bp = Blueprint("main", __name__)
def parse_board_size(raw_size, default=DEFAULT_BOARD_SIZE):
try:
board_size = int(raw_size)
except (TypeError, ValueError):
board_size = default
return max(MIN_BOARD_SIZE, min(MAX_BOARD_SIZE, board_size))
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 = []
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:
queen_solutions.append(queen_positions[:])
return
for col in range(board_size):
if is_queen_position_safe(row, col):
queen_positions[row] = col
place_queen(row + 1)
queen_positions[row] = -1
place_queen(0)
return queen_solutions
def solve_rooks(board_size):
rook_positions = list(range(board_size))
random.shuffle(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):
board = json.loads(game_state.board)
return {
"id": game_state.id,
"board": board,
"mode": game_state.mode,
"board_size": game_state.board_size,
"save_name": game_state.save_name,
"save_note": game_state.save_note,
"is_favorite": game_state.is_favorite,
"pieces_placed": sum(1 for column in board if column != -1),
"created_at": game_state.created_at.strftime("%Y-%m-%d %H:%M:%S"),
"updated_at": game_state.updated_at.strftime("%Y-%m-%d %H:%M:%S")
}
def build_save_point_query():
query = GameState.query.filter_by(user_id=current_user.id)
selected_mode = request.args.get("mode")
board_size = request.args.get("board_size")
favorites_only = request.args.get("favorites_only")
sort_by = request.args.get("sort", "newest")
if selected_mode in {"queens", "rooks"}:
query = query.filter_by(mode=selected_mode)
if board_size not in {None, "", "all"}:
query = query.filter_by(board_size=parse_board_size(board_size))
if parse_favorite_flag(favorites_only):
query = query.filter_by(is_favorite=True)
sort_options = {
"newest": GameState.created_at.desc(),
"oldest": GameState.created_at.asc(),
"updated": GameState.updated_at.desc(),
"favorites": GameState.is_favorite.desc()
}
return query.order_by(sort_options.get(sort_by, GameState.created_at.desc()), GameState.id.desc())
@main_bp.route("/") @main_bp.route("/")
def home(): def home():
return render_template("index.html") return render_template("index.html")
@main_bp.route('/dashboard')
@main_bp.route("/dashboard")
@login_required @login_required
def dashboard(): def dashboard():
return render_template('dashboard.html', user=current_user) return render_template("dashboard.html", user=current_user)
@main_bp.route("/check", methods=["POST"]) @main_bp.route("/check", methods=["POST"])
def check(): def check():
@@ -18,47 +229,138 @@ def check():
board = data["board"] board = data["board"]
row = data["row"] row = data["row"]
col = data["col"] col = data["col"]
piece_mode = data.get("mode", "queens")
for r in range(len(board)): for current_row, current_col in enumerate(board):
c = board[r] if current_col == -1 or current_row == row:
if c == -1 or r == row:
continue continue
# gleiche Spalte same_column = current_col == col
if c == col: same_diagonal = abs(current_col - col) == abs(current_row - row)
if same_column:
return jsonify({"valid": False}) return jsonify({"valid": False})
# gleiche Diagonale if piece_mode == "queens" and same_diagonal:
if abs(c - col) == abs(r - row):
return jsonify({"valid": False}) return jsonify({"valid": False})
return jsonify({"valid": True}) return jsonify({"valid": True})
@main_bp.route("/solve") @main_bp.route("/solve")
def solve(): def solve_queens_route():
n = 8 board_size = parse_board_size(request.args.get("size"))
solutions = [] return jsonify(solve_queens(board_size))
board = [-1] * n
def is_safe(row, col):
for r in range(row):
c = board[r]
if c == col or abs(c - col) == abs(r - row):
return False
return True
def backtrack(row): @main_bp.route("/solve_trace")
if row == n: def solve_queens_trace_route():
solutions.append(board[:]) board_size = parse_board_size(request.args.get("size"))
return return jsonify(build_queens_trace(board_size))
for col in range(n):
if is_safe(row, col):
board[row] = col
backtrack(row + 1)
board[row] = -1
backtrack(0) @main_bp.route("/solve_rooks")
return jsonify(solutions) 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():
data = request.json
board = data["board"]
piece_mode = data["mode"]
board_size = parse_board_size(data.get("boardSize"), default=len(board))
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,
board=json.dumps(board),
mode=piece_mode,
board_size=board_size,
save_name=save_name,
save_note=save_note,
is_favorite=is_favorite,
created_at=timestamp,
updated_at=timestamp
)
db.session.add(game_state)
db.session.commit()
return jsonify({
"status": "saved",
"save_point": serialize_game_state(game_state)
})
@main_bp.route("/save_points")
@login_required
def list_save_points():
save_points = build_save_point_query().all()
return jsonify([serialize_game_state(game_state) for game_state in save_points])
@main_bp.route("/save_points/<int:game_id>/favorite", methods=["POST"])
@login_required
def toggle_save_point_favorite(game_id):
game_state = GameState.query.filter_by(id=game_id, user_id=current_user.id).first()
if not game_state:
return jsonify({"error": "Save point not found"}), 404
game_state.is_favorite = not game_state.is_favorite
db.session.commit()
return jsonify(serialize_game_state(game_state))
@main_bp.route("/save_points/<int:game_id>", methods=["DELETE"])
@login_required
def delete_save_point(game_id):
game_state = GameState.query.filter_by(id=game_id, user_id=current_user.id).first()
if not game_state:
return jsonify({"error": "Save point not found"}), 404
db.session.delete(game_state)
db.session.commit()
return jsonify({"status": "deleted"})
@main_bp.route("/load")
@login_required
def load_game():
latest_game = (
GameState.query.filter_by(user_id=current_user.id)
.order_by(GameState.created_at.desc(), GameState.id.desc())
.first()
)
if not latest_game:
return jsonify({"board": [-1] * DEFAULT_BOARD_SIZE, "mode": "queens"})
return jsonify(serialize_game_state(latest_game))
@main_bp.route("/load/<int:game_id>")
@login_required
def load_specific_game(game_id):
game_state = GameState.query.filter_by(id=game_id, user_id=current_user.id).first()
if not game_state:
return jsonify({"error": "Save point not found"}), 404
return jsonify(serialize_game_state(game_state))
+337 -7
View File
@@ -44,16 +44,30 @@ body {
text-align: center; text-align: center;
} }
#statusMessage {
min-height: 24px;
margin: 0 0 8px;
font-weight: bold;
}
.board {
overflow-x: auto;
padding-bottom: 8px;
}
/* ♟️ Board */ /* ♟️ Board */
.board-grid { .board-grid {
--label-size: 30px;
--cell-size: 60px;
display: grid; display: grid;
grid-template-columns: 30px repeat(8, 60px); grid-template-columns: var(--label-size) repeat(var(--board-size, 8), var(--cell-size));
grid-template-rows: 30px repeat(8, 60px); grid-template-rows: var(--label-size) repeat(var(--board-size, 8), var(--cell-size));
margin: 20px auto; margin: 20px auto;
width: max-content; width: max-content;
border: 6px solid #1e293b; border: 6px solid #1e293b;
border-radius: 10px; border-radius: 10px;
overflow: hidden; overflow: hidden;
max-width: 100%;
} }
.label { .label {
@@ -63,17 +77,18 @@ body {
font-weight: bold; font-weight: bold;
background: #aec8eb; background: #aec8eb;
color: #1e293b; color: #1e293b;
width: var(--label-size);
height: var(--label-size);
font-size: 14px;
} }
.cell { .cell {
width: 60px; width: var(--cell-size);
height: 60px; height: var(--cell-size);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
font-size: 30px; font-size: calc(var(--cell-size) * 0.5);
cursor: pointer; cursor: pointer;
transition: 0.2s; transition: 0.2s;
} }
@@ -82,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;
} }
@@ -98,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 {
@@ -114,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;
} }
@@ -141,3 +181,293 @@ body {
margin: 4px 0; margin: 4px 0;
border-radius: 6px; border-radius: 6px;
} }
.mode-switch {
margin-bottom: 15px;
}
.mode-switch .btn {
margin: 5px;
}
.btn.active {
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;
}
.sidebar {
position: fixed;
top: 0;
right: 0;
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;
}
.sidebar.open {
transform: translateX(0);
}
.save-points-sidebar {
width: min(380px, 92vw);
}
.save-sidebar {
width: min(360px, 90vw);
}
.sidebar-header {
display: flex;
justify-content: space-between;
align-items: center;
gap: 10px;
}
.sidebar-close {
margin: 0;
}
.sidebar-label {
display: block;
margin-top: 18px;
margin-bottom: 8px;
font-weight: bold;
}
.sidebar input[type="range"] {
width: 100%;
}
.sidebar-input {
width: 100%;
padding: 10px 12px;
border: 1px solid #cbd5e1;
border-radius: 8px;
font: inherit;
box-sizing: border-box;
background: #fff;
}
.sidebar-textarea {
resize: vertical;
min-height: 96px;
}
.sidebar-checkbox {
display: flex;
align-items: center;
gap: 10px;
margin: 16px 0 18px;
color: #334155;
}
.sidebar-size-row {
margin: 12px 0 18px;
font-size: 18px;
}
.sidebar-hint {
color: #64748b;
font-size: 14px;
}
.save-points-list {
display: grid;
gap: 12px;
margin-top: 18px;
}
.save-point-filters {
display: grid;
gap: 8px;
margin-top: 18px;
padding-bottom: 16px;
border-bottom: 1px solid #e2e8f0;
}
.save-point-card {
border: 1px solid #dbe4ee;
border-radius: 10px;
padding: 14px;
background: #f8fafc;
}
.save-point-card h4 {
margin: 0 0 8px;
color: #1e293b;
}
.save-point-meta {
margin: 4px 0;
color: #475569;
font-size: 14px;
}
.save-point-preview {
margin: 10px 0 0;
color: #334155;
font-size: 14px;
word-break: break-word;
}
.save-point-note {
margin: 10px 0 0;
color: #475569;
font-size: 14px;
}
.save-point-actions {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 14px;
}
.save-point-actions .btn {
margin: 0;
}
.btn.ghost {
background: #e2e8f0;
color: #1e293b;
}
.btn.ghost:hover {
background: #cbd5e1;
}
.sidebar-backdrop {
position: fixed;
inset: 0;
background: rgba(15, 23, 42, 0.3);
opacity: 0;
pointer-events: none;
transition: opacity 0.25s ease;
z-index: 20;
}
.sidebar-backdrop.visible {
opacity: 1;
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;
}
.navbar {
padding: 15px 18px;
}
.solver-panel-header,
.solver-speed {
grid-template-columns: 1fr;
display: grid;
}
.solver-step-info,
.solver-speed-value {
justify-self: start;
}
}
+880
View File
@@ -0,0 +1,880 @@
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");
const saveBackdrop = document.getElementById("saveBackdrop");
const savePointsSidebar = document.getElementById("savePointsSidebar");
const savePointsBackdrop = document.getElementById("savePointsBackdrop");
const boardSizeInput = document.getElementById("boardSizeInput");
const boardSizeValue = document.getElementById("boardSizeValue");
const boardSizeValueMirror = document.getElementById("boardSizeValueMirror");
const sidebarBoardSizeValue = document.getElementById("sidebarBoardSizeValue");
const sidebarBoardSizeValueMirror = document.getElementById("sidebarBoardSizeValueMirror");
const savePointsList = document.getElementById("savePointsList");
const saveNameInput = document.getElementById("saveNameInput");
const saveNoteInput = document.getElementById("saveNoteInput");
const saveFavoriteInput = document.getElementById("saveFavoriteInput");
const savePointModeFilter = document.getElementById("savePointModeFilter");
const savePointSizeFilter = document.getElementById("savePointSizeFilter");
const savePointSortFilter = document.getElementById("savePointSortFilter");
const savePointFavoritesOnly = document.getElementById("savePointFavoritesOnly");
function createEmptyBoard(size) {
return Array(size).fill(EMPTY_CELL);
}
function clampBoardSize(size) {
return Math.min(MAX_BOARD_SIZE, Math.max(MIN_BOARD_SIZE, Number(size)));
}
function getColumnLabel(index) {
return String.fromCharCode(65 + index);
}
function updateModeButtons() {
btnQueens.classList.toggle("active", mode === "queens");
btnRooks.classList.toggle("active", mode === "rooks");
}
function updateBoardSizeDisplays(size) {
boardSizeValue.textContent = size;
boardSizeValueMirror.textContent = size;
sidebarBoardSizeValue.textContent = size;
sidebarBoardSizeValueMirror.textContent = size;
boardSizeInput.value = size;
}
function setStatus(message = "", color = "") {
statusMessage.textContent = message;
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)));
boardDiv.style.setProperty("--board-size", boardSize);
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;
updateModeButtons();
resetBoard({
statusMessageText: `${getPieceLabel(mode)}-Modus aktiviert.`,
statusColor: "#2563eb",
solverMessageText: `${getPieceLabel(mode)}-Solver bereit. ${DEFAULT_SOLVER_MESSAGE}`
});
}
function closeAllPanels() {
toggleSidebar(false);
toggleSavePanel(false);
toggleSavePointsPanel(false);
}
function getConflicts() {
const conflicts = [];
for (let rowA = 0; rowA < boardSize; rowA++) {
if (board[rowA] === EMPTY_CELL) {
continue;
}
for (let rowB = rowA + 1; rowB < boardSize; rowB++) {
if (board[rowB] === EMPTY_CELL) {
continue;
}
const colA = board[rowA];
const colB = board[rowB];
const sameColumn = colA === colB;
const sameDiagonal = Math.abs(colA - colB) === Math.abs(rowA - rowB);
const hasConflict = mode === "rooks" ? sameColumn : sameColumn || sameDiagonal;
if (hasConflict) {
conflicts.push([rowA, colA], [rowB, colB]);
}
}
}
return conflicts;
}
function checkSolution() {
const allPlaced = board.every((col) => col !== EMPTY_CELL);
if (!allPlaced) {
setStatus("Noch nicht alle Figuren platziert.", "#d97706");
return;
}
if (getConflicts().length > 0) {
setStatus("Es gibt Konflikte auf dem Brett.", "red");
return;
}
setStatus("Perfekt geloest.", "green");
}
function drawBoard() {
boardDiv.innerHTML = "";
boardDiv.className = "board-grid";
updateBoardLayout();
const conflicts = getConflicts();
const highlight = currentSolverHighlight;
for (let row = -1; row < boardSize; row++) {
for (let col = -1; col < boardSize; col++) {
const cell = document.createElement("div");
if (row === -1 && col === -1) {
cell.classList.add("label");
} else if (row === -1) {
cell.textContent = getColumnLabel(col);
cell.classList.add("label");
} else if (col === -1) {
cell.textContent = boardSize - row;
cell.classList.add("label");
} 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" ? "♛" : "♜";
if (conflicts.some(([conflictRow, conflictCol]) => conflictRow === row && conflictCol === col)) {
cell.classList.add("invalid");
}
}
cell.onclick = () => placeQueen(row, col);
}
boardDiv.appendChild(cell);
}
}
updateQueenList();
}
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();
}
function previewBoardSize(size) {
pendingBoardSize = clampBoardSize(size);
sidebarBoardSizeValue.textContent = pendingBoardSize;
sidebarBoardSizeValueMirror.textContent = pendingBoardSize;
}
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();
toggleSidebar(false);
}
function toggleSidebar(forceOpen) {
const shouldOpen = typeof forceOpen === "boolean" ? forceOpen : !settingsSidebar.classList.contains("open");
if (shouldOpen) {
toggleSavePanel(false);
toggleSavePointsPanel(false);
}
settingsSidebar.classList.toggle("open", shouldOpen);
sidebarBackdrop.classList.toggle("visible", shouldOpen);
settingsSidebar.setAttribute("aria-hidden", String(!shouldOpen));
}
function toggleSavePanel(forceOpen) {
const shouldOpen = typeof forceOpen === "boolean" ? forceOpen : !saveSidebar.classList.contains("open");
if (shouldOpen) {
toggleSidebar(false);
toggleSavePointsPanel(false);
}
saveSidebar.classList.toggle("open", shouldOpen);
saveBackdrop.classList.toggle("visible", shouldOpen);
saveSidebar.setAttribute("aria-hidden", String(!shouldOpen));
}
function toggleSavePointsPanel(forceOpen) {
const shouldOpen = typeof forceOpen === "boolean" ? forceOpen : !savePointsSidebar.classList.contains("open");
if (shouldOpen) {
toggleSidebar(false);
toggleSavePanel(false);
}
savePointsSidebar.classList.toggle("open", shouldOpen);
savePointsBackdrop.classList.toggle("visible", shouldOpen);
savePointsSidebar.setAttribute("aria-hidden", String(!shouldOpen));
}
function getPieceLabel(selectedMode) {
return selectedMode === "queens" ? "Dame" : "Turm";
}
function getPlacedPieceCount() {
return board.filter((col) => col !== EMPTY_CELL).length;
}
function resetSaveForm() {
saveNameInput.value = `${getPieceLabel(mode)} ${boardSize}x${boardSize} - ${getPlacedPieceCount()} gesetzt`;
saveNoteInput.value = "";
saveFavoriteInput.checked = false;
}
function openSavePanel() {
resetSaveForm();
toggleSavePanel(true);
}
function getBoardPreview(savedBoard, limit = 4) {
const preview = [];
for (let row = 0; row < savedBoard.length; row++) {
if (savedBoard[row] === EMPTY_CELL) {
continue;
}
preview.push(`${getColumnLabel(savedBoard[row])}${savedBoard.length - row}`);
if (preview.length === limit) {
break;
}
}
return preview.length > 0 ? preview.join(", ") : "Noch keine Figuren gesetzt";
}
function getSavePointFilters() {
const params = new URLSearchParams();
if (savePointModeFilter.value !== "all") {
params.set("mode", savePointModeFilter.value);
}
if (savePointSizeFilter.value !== "all") {
params.set("board_size", savePointSizeFilter.value);
}
params.set("sort", savePointSortFilter.value);
if (savePointFavoritesOnly.checked) {
params.set("favorites_only", "true");
}
return params.toString();
}
function createSavePointActionButton(label, className, onClick) {
const button = document.createElement("button");
button.className = className;
button.type = "button";
button.textContent = label;
button.onclick = onClick;
return button;
}
function renderSavePoints(savePoints) {
savePointsList.innerHTML = "";
if (savePoints.length === 0) {
savePointsList.innerHTML = '<p class="sidebar-hint">Keine Speicherstaende fuer diese Filter gefunden.</p>';
return;
}
for (const savePoint of savePoints) {
const item = document.createElement("article");
const title = document.createElement("h4");
const createdAt = document.createElement("p");
const updatedAt = document.createElement("p");
const boardMeta = document.createElement("p");
const preview = document.createElement("p");
const note = document.createElement("p");
const actions = document.createElement("div");
item.className = "save-point-card";
title.textContent = `${savePoint.is_favorite ? "★ " : ""}${savePoint.save_name}`;
createdAt.className = "save-point-meta";
createdAt.textContent = `Erstellt: ${savePoint.created_at}`;
updatedAt.className = "save-point-meta";
updatedAt.textContent = `Aktualisiert: ${savePoint.updated_at}`;
boardMeta.className = "save-point-meta";
boardMeta.textContent = `${getPieceLabel(savePoint.mode)} | ${savePoint.board_size} x ${savePoint.board_size} | ${savePoint.pieces_placed}/${savePoint.board_size} gesetzt`;
preview.className = "save-point-preview";
preview.textContent = `Vorschau: ${getBoardPreview(savePoint.board)}`;
note.className = "save-point-note";
note.textContent = savePoint.save_note ? `Notiz: ${savePoint.save_note}` : "Notiz: Keine Notiz";
actions.className = "save-point-actions";
actions.appendChild(createSavePointActionButton("Laden", "btn", () => loadSavePoint(savePoint.id)));
actions.appendChild(createSavePointActionButton(
savePoint.is_favorite ? "Favorit entfernen" : "Als Favorit",
"btn secondary",
() => toggleSavePointFavorite(savePoint.id)
));
actions.appendChild(createSavePointActionButton("Loeschen", "btn ghost", () => deleteSavePoint(savePoint.id)));
item.appendChild(title);
item.appendChild(createdAt);
item.appendChild(updatedAt);
item.appendChild(boardMeta);
item.appendChild(preview);
item.appendChild(note);
item.appendChild(actions);
savePointsList.appendChild(item);
}
}
async function refreshSavePoints() {
const queryString = getSavePointFilters();
const response = await fetch(`/save_points${queryString ? `?${queryString}` : ""}`);
const savePoints = await response.json();
renderSavePoints(savePoints);
}
async function openSavePointsPanel() {
await refreshSavePoints();
toggleSavePointsPanel(true);
}
function applyLoadedGame(data) {
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 === "rooks" ? "rooks" : "queens";
currentSolverHighlight = null;
updateModeButtons();
updateBoardSizeDisplays(boardSize);
drawBoard();
}
function applySolverStep(step) {
board = Array.isArray(step.board) ? step.board.slice() : createEmptyBoard(boardSize);
if (Number.isInteger(step.row) && Number.isInteger(step.col)) {
currentSolverHighlight = {
row: step.row,
col: step.col,
type: step.type
};
} else if (step.type === "solution" && solverStepIndex > 0) {
const previousStep = solverSteps[solverStepIndex - 1];
currentSolverHighlight = Number.isInteger(previousStep.row) && Number.isInteger(previousStep.col)
? {
row: previousStep.row,
col: previousStep.col,
type: "solution"
}
: null;
} else {
currentSolverHighlight = null;
}
setSolverMessage(step.message || DEFAULT_SOLVER_MESSAGE);
drawBoard();
updateSolverControls();
}
function 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();
}
function updateQueenList() {
queenList.innerHTML = "";
let count = 1;
for (let row = 0; row < boardSize; row++) {
if (board[row] === EMPTY_CELL) {
continue;
}
const col = board[row];
const position = `${getColumnLabel(col)}${boardSize - row}`;
const li = document.createElement("li");
li.textContent = `${mode === "queens" ? "Dame" : "Turm"} ${count}: ${position}`;
queenList.appendChild(li);
count += 1;
}
}
async function saveGame() {
const response = await fetch("/save", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
board,
mode,
boardSize,
saveName: saveNameInput.value,
saveNote: saveNoteInput.value,
isFavorite: saveFavoriteInput.checked
})
});
if (!response.ok) {
setStatus("Der Speicherstand konnte nicht gespeichert werden.", "red");
return;
}
const data = await response.json();
toggleSavePanel(false);
await refreshSavePoints();
setStatus(`Speicherpunkt "${data.save_point.save_name}" gespeichert.`, "green");
}
async function loadGame() {
const response = await fetch("/load");
const data = await response.json();
applyLoadedGame(data);
}
async function loadSavePoint(savePointId) {
const response = await fetch(`/load/${savePointId}`);
if (!response.ok) {
setStatus("Dieser Speicherstand konnte nicht geladen werden.", "red");
return;
}
const data = await response.json();
applyLoadedGame(data);
setStatus(`Speicherpunkt "${data.save_name}" geladen.`, "green");
toggleSavePointsPanel(false);
}
async function toggleSavePointFavorite(savePointId) {
const response = await fetch(`/save_points/${savePointId}/favorite`, {
method: "POST"
});
if (!response.ok) {
setStatus("Favorit konnte nicht aktualisiert werden.", "red");
return;
}
await refreshSavePoints();
setStatus("Favoritenstatus aktualisiert.", "#2563eb");
}
async function deleteSavePoint(savePointId) {
const confirmed = window.confirm("Diesen Speicherpunkt wirklich loeschen?");
if (!confirmed) {
return;
}
const response = await fetch(`/save_points/${savePointId}`, {
method: "DELETE"
});
if (!response.ok) {
setStatus("Speicherpunkt konnte nicht geloescht werden.", "red");
return;
}
await refreshSavePoints();
setStatus("Speicherpunkt geloescht.", "#2563eb");
}
pendingBoardSize = boardSize;
updateModeButtons();
updateBoardSizeDisplays(boardSize);
updateSolverSpeed(solverSpeedMs);
setSolverMessage(`${getPieceLabel(mode)}-Solver bereit. ${DEFAULT_SOLVER_MESSAGE}`);
updateSolverControls();
drawBoard();
+123 -136
View File
@@ -45,163 +45,150 @@
</form> </form>
</div> </div>
<div class="mode-switch">
<button class="btn active" onclick="setMode('queens')" id="btnQueens">♛ Damen</button>
<button class="btn secondary" onclick="setMode('rooks')" id="btnRooks">♜ Türme</button>
<button class="btn secondary" onclick="toggleSidebar()" id="btnSidebar">Board Settings</button>
</div>
<!-- ♟️ Game Card --> <!-- ♟️ Game Card -->
<div class="card game-card"> <div class="card game-card">
<h2>♟️ 8-Damen Spiel</h2> <h2>♟️ Damen- und Tuerme-Spiel</h2>
<p class="board-size-info">Aktuelle Brettgroesse: <span id="boardSizeValue">8</span> x <span
id="boardSizeValueMirror">8</span></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 secondary" onclick="resetBoard()">Reset</button> <button class="btn secondary" onclick="resetBoard()">Reset</button>
<button class="btn" onclick="openSavePanel()">💾 Save</button>
<button class="btn" onclick="openSavePointsPanel()">📂 Load</button>
</div> </div>
</div> </div>
</div>
<script>
let board = Array(8).fill(-1);
const boardDiv = document.getElementById("board");
function getConflicts() {
let conflicts = [];
for (let r1 = 0; r1 < 8; r1++) {
if (board[r1] === -1) continue;
for (let r2 = r1 + 1; r2 < 8; r2++) {
if (board[r2] === -1) continue;
let c1 = board[r1];
let c2 = board[r2];
if (
c1 === c2 ||
Math.abs(c1 - c2) === Math.abs(r1 - r2)
) {
conflicts.push([r1, c1]);
conflicts.push([r2, c2]);
}
}
}
return conflicts;
}
function drawBoard() {
boardDiv.innerHTML = "";
boardDiv.className = "board-grid";
const conflicts = getConflicts();
const letters = ["A", "B", "C", "D", "E", "F", "G", "H"];
for (let row = -1; row < 8; row++) {
for (let col = -1; col < 8; col++) {
const cell = document.createElement("div");
// 🔹 Ecke oben links
if (row === -1 && col === -1) {
cell.classList.add("label");
}
// 🔹 Buchstaben (oben)
else if (row === -1) {
cell.textContent = letters[col];
cell.classList.add("label");
}
// 🔹 Zahlen (links)
else if (col === -1) {
cell.textContent = 8 - row;
cell.classList.add("label");
}
// 🔹 echtes Spielfeld
else {
cell.classList.add("cell");
if ((row + col) % 2 === 0) {
cell.classList.add("white");
} else {
cell.classList.add("black");
}
if (board[row] === col) {
cell.textContent = "♛";
if (conflicts.some(([r, c]) => r === row && c === col)) {
cell.classList.add("invalid");
}
}
cell.onclick = () => placeQueen(row, col);
}
boardDiv.appendChild(cell);
}
}
updateQueenList();
}
function placeQueen(row, col) {
if (board[row] === col) {
board[row] = -1;
} else {
board[row] = col;
}
drawBoard();
updateQueenList();
}
async function solve() {
const res = await fetch("/solve");
const solutions = await res.json();
board = solutions[0];
drawBoard();
}
function resetBoard() {
board = Array(8).fill(-1);
drawBoard();
updateQueenList();
}
drawBoard();
</script>
<div class="card"> <div class="card">
<h3>📋 Damen Positionen</h3> <h3>📋 Figuren Positionen</h3>
<ul id="queenList"></ul> <ul id="queenList"></ul>
</div> </div>
</div>
<script> <aside class="sidebar" id="settingsSidebar" aria-hidden="true">
function updateQueenList() { <div class="sidebar-header">
const list = document.getElementById("queenList"); <h3>Brettgroesse</h3>
list.innerHTML = ""; <button class="btn secondary sidebar-close" onclick="toggleSidebar(false)" type="button">Schliessen</button>
</div>
const letters = ["A", "B", "C", "D", "E", "F", "G", "H"]; <p>Wähle eine Grösse zwischen 4 und 13.</p>
let count = 1; <label class="sidebar-label" for="boardSizeInput">Groesse</label>
<input id="boardSizeInput" type="range" min="4" max="13" value="8" oninput="previewBoardSize(this.value)">
<div class="sidebar-size-row">
<strong><span id="sidebarBoardSizeValue">8</span> x <span id="sidebarBoardSizeValueMirror">8</span></strong>
</div>
for (let row = 0; row < 8; row++) { <button class="btn" onclick="applyBoardSize()" type="button">Apply Size</button>
if (board[row] !== -1) { <p class="sidebar-hint">Das Brett wird beim Ändern zur Sicherheit zurückgesetzt.</p>
let col = board[row]; </aside>
let pos = letters[col] + (8 - row); <aside class="sidebar save-sidebar" id="saveSidebar" aria-hidden="true">
<div class="sidebar-header">
<h3>Speicherpunkt</h3>
<button class="btn secondary sidebar-close" onclick="toggleSavePanel(false)" type="button">Schliessen</button>
</div>
const li = document.createElement("li"); <p>Gib dem Spielstand einen klaren Namen und optional eine kurze Notiz.</p>
li.textContent = `Dame ${count}: ${pos}`;
list.appendChild(li); <label class="sidebar-label" for="saveNameInput">Name</label>
count++; <input class="sidebar-input" id="saveNameInput" type="text" maxlength="80" placeholder="z. B. Fast geloeste Dame 8x8">
}
}
}
</script> <label class="sidebar-label" for="saveNoteInput">Notiz</label>
<textarea class="sidebar-input sidebar-textarea" id="saveNoteInput" rows="4" maxlength="240"
placeholder="z. B. Gute Startposition fuer Tuermetest"></textarea>
<label class="sidebar-checkbox">
<input id="saveFavoriteInput" type="checkbox">
Als Favorit markieren
</label>
<button class="btn" onclick="saveGame()" type="button">Jetzt speichern</button>
</aside>
<aside class="sidebar save-points-sidebar" id="savePointsSidebar" aria-hidden="true">
<div class="sidebar-header">
<h3>Save Points</h3>
<button class="btn secondary sidebar-close" onclick="toggleSavePointsPanel(false)" type="button">Schliessen</button>
</div>
<p>Waehle einen Speicherstand aus der Liste.</p>
<div class="save-point-filters">
<label class="sidebar-label" for="savePointModeFilter">Modus</label>
<select class="sidebar-input" id="savePointModeFilter" onchange="refreshSavePoints()">
<option value="all">Alle</option>
<option value="queens">Damen</option>
<option value="rooks">Tuerme</option>
</select>
<label class="sidebar-label" for="savePointSizeFilter">Brettgroesse</label>
<select class="sidebar-input" id="savePointSizeFilter" onchange="refreshSavePoints()">
<option value="all">Alle</option>
{% for board_size in range(4, 14) %}
<option value="{{ board_size }}">{{ board_size }} x {{ board_size }}</option>
{% endfor %}
</select>
<label class="sidebar-label" for="savePointSortFilter">Sortierung</label>
<select class="sidebar-input" id="savePointSortFilter" onchange="refreshSavePoints()">
<option value="newest">Neueste zuerst</option>
<option value="oldest">Aelteste zuerst</option>
<option value="updated">Zuletzt bearbeitet</option>
<option value="favorites">Favoriten zuerst</option>
</select>
<label class="sidebar-checkbox">
<input id="savePointFavoritesOnly" type="checkbox" onchange="refreshSavePoints()">
Nur Favoriten anzeigen
</label>
</div>
<div class="save-points-list" id="savePointsList">
<p class="sidebar-hint">Noch keine Speicherstaende gefunden.</p>
</div>
</aside>
<div class="sidebar-backdrop" id="sidebarBackdrop" onclick="toggleSidebar(false)"></div>
<div class="sidebar-backdrop" id="saveBackdrop" onclick="toggleSavePanel(false)"></div>
<div class="sidebar-backdrop" id="savePointsBackdrop" onclick="toggleSavePointsPanel(false)"></div>
<script src="../static/js/dashboard.js"></script>
</body> </body>
+13
View File
@@ -5,3 +5,16 @@ CREATE TABLE users (
email VARCHAR(120) NOT NULL UNIQUE, email VARCHAR(120) NOT NULL UNIQUE,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP created_at DATETIME DEFAULT CURRENT_TIMESTAMP
); );
CREATE TABLE game_state (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
board TEXT NOT NULL, -- JSON String (z.B. "[0,4,7,5,2,6,1,3]")
mode TEXT NOT NULL, -- "queens" oder "rooks"
board_size INTEGER NOT NULL DEFAULT 8,
save_name VARCHAR(80) NOT NULL DEFAULT 'Unnamed Save',
save_note TEXT NOT NULL DEFAULT '',
is_favorite BOOLEAN NOT NULL DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);