Merge pull request #13 from ScriptSorcerer20/12-fortschritt-abspeichern
12 fortschritt abspeichern
This commit is contained in:
@@ -20,6 +20,7 @@ def create_app():
|
||||
login_manager.login_view = "login.login"
|
||||
|
||||
from .models.user import User
|
||||
from .models.gamestate import GameState
|
||||
|
||||
with app.app_context():
|
||||
db.create_all()
|
||||
|
||||
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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.
@@ -46,7 +46,7 @@ def login():
|
||||
|
||||
return render_template("login.html")
|
||||
|
||||
@login_bp.route('/logout')
|
||||
@login_bp.route('/logout', methods=['POST'])
|
||||
@login_required
|
||||
def logout():
|
||||
logout_user()
|
||||
|
||||
+213
-41
@@ -1,17 +1,123 @@
|
||||
from flask import Blueprint, jsonify, render_template, request
|
||||
from flask_login import login_required, current_user
|
||||
import json
|
||||
import random
|
||||
|
||||
from flask import Blueprint, jsonify, render_template, request
|
||||
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__)
|
||||
|
||||
|
||||
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 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 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("/")
|
||||
def home():
|
||||
return render_template("index.html")
|
||||
|
||||
@main_bp.route('/dashboard')
|
||||
|
||||
@main_bp.route("/dashboard")
|
||||
@login_required
|
||||
def dashboard():
|
||||
return render_template('dashboard.html', user=current_user)
|
||||
return render_template("dashboard.html", user=current_user)
|
||||
|
||||
|
||||
@main_bp.route("/check", methods=["POST"])
|
||||
def check():
|
||||
@@ -19,57 +125,123 @@ def check():
|
||||
board = data["board"]
|
||||
row = data["row"]
|
||||
col = data["col"]
|
||||
piece_mode = data.get("mode", "queens")
|
||||
|
||||
for r in range(len(board)):
|
||||
c = board[r]
|
||||
|
||||
if c == -1 or r == row:
|
||||
for current_row, current_col in enumerate(board):
|
||||
if current_col == -1 or current_row == row:
|
||||
continue
|
||||
|
||||
# gleiche Spalte
|
||||
if c == col:
|
||||
same_column = current_col == col
|
||||
same_diagonal = abs(current_col - col) == abs(current_row - row)
|
||||
|
||||
if same_column:
|
||||
return jsonify({"valid": False})
|
||||
|
||||
# gleiche Diagonale
|
||||
if abs(c - col) == abs(r - row):
|
||||
if piece_mode == "queens" and same_diagonal:
|
||||
return jsonify({"valid": False})
|
||||
|
||||
return jsonify({"valid": True})
|
||||
|
||||
|
||||
@main_bp.route("/solve")
|
||||
def solve():
|
||||
n = 8
|
||||
solutions = []
|
||||
board = [-1] * n
|
||||
def solve_queens_route():
|
||||
board_size = parse_board_size(request.args.get("size"))
|
||||
return jsonify(solve_queens(board_size))
|
||||
|
||||
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):
|
||||
if row == n:
|
||||
solutions.append(board[:])
|
||||
return
|
||||
|
||||
for col in range(n):
|
||||
if is_safe(row, col):
|
||||
board[row] = col
|
||||
backtrack(row + 1)
|
||||
board[row] = -1
|
||||
|
||||
backtrack(0)
|
||||
return jsonify(solutions)
|
||||
|
||||
@main_bp.route("/solve_rooks")
|
||||
def solve_rooks_route():
|
||||
return jsonify(solve_rooks(8))
|
||||
board_size = parse_board_size(request.args.get("size"))
|
||||
return jsonify(solve_rooks(board_size))
|
||||
|
||||
def solve_rooks(n=8):
|
||||
solution = list(range(n))
|
||||
random.shuffle(solution)
|
||||
return solution
|
||||
|
||||
@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"))
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
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))
|
||||
|
||||
@@ -44,16 +44,24 @@ body {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.board {
|
||||
overflow-x: auto;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
/* ♟️ Board */
|
||||
.board-grid {
|
||||
--label-size: 30px;
|
||||
--cell-size: 60px;
|
||||
display: grid;
|
||||
grid-template-columns: 30px repeat(8, 60px);
|
||||
grid-template-rows: 30px repeat(8, 60px);
|
||||
grid-template-columns: var(--label-size) repeat(var(--board-size, 8), var(--cell-size));
|
||||
grid-template-rows: var(--label-size) repeat(var(--board-size, 8), var(--cell-size));
|
||||
margin: 20px auto;
|
||||
width: max-content;
|
||||
border: 6px solid #1e293b;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.label {
|
||||
@@ -63,17 +71,18 @@ body {
|
||||
font-weight: bold;
|
||||
background: #aec8eb;
|
||||
color: #1e293b;
|
||||
width: var(--label-size);
|
||||
height: var(--label-size);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.cell {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
width: var(--cell-size);
|
||||
height: var(--cell-size);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 30px;
|
||||
font-size: calc(var(--cell-size) * 0.5);
|
||||
cursor: pointer;
|
||||
transition: 0.2s;
|
||||
}
|
||||
@@ -152,4 +161,180 @@ body {
|
||||
|
||||
.btn.active {
|
||||
background: #22c55e;
|
||||
}
|
||||
}
|
||||
|
||||
.board-size-info {
|
||||
margin: 0 0 10px;
|
||||
color: #475569;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: min(320px, 85vw);
|
||||
height: 100vh;
|
||||
padding: 24px;
|
||||
background: white;
|
||||
box-shadow: -10px 0 24px rgba(15, 23, 42, 0.18);
|
||||
transform: translateX(100%);
|
||||
transition: transform 0.25s ease;
|
||||
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;
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.container {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
padding: 15px 18px;
|
||||
}
|
||||
}
|
||||
|
||||
+411
-116
@@ -1,53 +1,110 @@
|
||||
let mode = "queens"; // oder "rooks"
|
||||
const DEFAULT_BOARD_SIZE = 8;
|
||||
const MIN_BOARD_SIZE = 4;
|
||||
const MAX_BOARD_SIZE = 13;
|
||||
const EMPTY_CELL = -1;
|
||||
|
||||
let mode = "queens";
|
||||
let boardSize = DEFAULT_BOARD_SIZE;
|
||||
let pendingBoardSize = DEFAULT_BOARD_SIZE;
|
||||
let board = createEmptyBoard(boardSize);
|
||||
|
||||
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 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 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 setMode(newMode) {
|
||||
mode = newMode;
|
||||
resetBoard();
|
||||
|
||||
document.getElementById("btnQueens").classList.remove("active");
|
||||
document.getElementById("btnRooks").classList.remove("active");
|
||||
|
||||
if (mode === "queens") {
|
||||
document.getElementById("btnQueens").classList.add("active");
|
||||
} else {
|
||||
document.getElementById("btnRooks").classList.add("active");
|
||||
}
|
||||
|
||||
drawBoard();
|
||||
updateModeButtons();
|
||||
}
|
||||
|
||||
let board = Array(8).fill(-1);
|
||||
const boardDiv = document.getElementById("board");
|
||||
function closeAllPanels() {
|
||||
toggleSidebar(false);
|
||||
toggleSavePanel(false);
|
||||
toggleSavePointsPanel(false);
|
||||
}
|
||||
|
||||
function getConflicts() {
|
||||
let conflicts = [];
|
||||
const conflicts = [];
|
||||
|
||||
for (let r1 = 0; r1 < 8; r1++) {
|
||||
if (board[r1] === -1) continue;
|
||||
for (let rowA = 0; rowA < boardSize; rowA++) {
|
||||
if (board[rowA] === EMPTY_CELL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (let r2 = r1 + 1; r2 < 8; r2++) {
|
||||
if (board[r2] === -1) continue;
|
||||
|
||||
let c1 = board[r1];
|
||||
let c2 = board[r2];
|
||||
|
||||
// 🔴 Türme: nur gleiche Spalte
|
||||
if (mode === "rooks") {
|
||||
if (c1 === c2) {
|
||||
conflicts.push([r1, c1]);
|
||||
conflicts.push([r2, c2]);
|
||||
}
|
||||
for (let rowB = rowA + 1; rowB < boardSize; rowB++) {
|
||||
if (board[rowB] === EMPTY_CELL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 🔴 Damen: Spalte + Diagonale
|
||||
else if (mode === "queens") {
|
||||
if (
|
||||
c1 === c2 ||
|
||||
Math.abs(c1 - c2) === Math.abs(r1 - r2)
|
||||
) {
|
||||
conflicts.push([r1, c1]);
|
||||
conflicts.push([r2, c2]);
|
||||
}
|
||||
const colA = board[rowA];
|
||||
const colB = board[rowB];
|
||||
const sameColumn = colA === colB;
|
||||
const sameDiagonal = Math.abs(colA - colB) === Math.abs(rowA - rowB);
|
||||
|
||||
// Rooks only attack vertically, queens also attack diagonally.
|
||||
const hasConflict = mode === "rooks" ? sameColumn : sameColumn || sameDiagonal;
|
||||
|
||||
if (hasConflict) {
|
||||
conflicts.push([rowA, colA], [rowB, colB]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -56,71 +113,48 @@ function getConflicts() {
|
||||
}
|
||||
|
||||
function checkSolution() {
|
||||
const status = document.getElementById("statusMessage");
|
||||
|
||||
const allPlaced = board.every(col => col !== -1);
|
||||
const allPlaced = board.every((col) => col !== EMPTY_CELL);
|
||||
|
||||
if (!allPlaced) {
|
||||
status.textContent = "❗ Noch nicht alle Figuren platziert.";
|
||||
status.style.color = "orange";
|
||||
setStatus("❗ Noch nicht alle Figuren platziert.", "orange");
|
||||
return;
|
||||
}
|
||||
|
||||
const conflicts = getConflicts();
|
||||
|
||||
if (conflicts.length > 0) {
|
||||
status.textContent = "❌ Es gibt Konflikte!";
|
||||
status.style.color = "red";
|
||||
if (getConflicts().length > 0) {
|
||||
setStatus("❌ Es gibt Konflikte!", "red");
|
||||
return;
|
||||
}
|
||||
|
||||
status.textContent = "🎉 Perfekt gelöst!";
|
||||
status.style.color = "green";
|
||||
setStatus("🎉 Perfekt geloest!", "green");
|
||||
}
|
||||
|
||||
function drawBoard() {
|
||||
boardDiv.innerHTML = "";
|
||||
boardDiv.className = "board-grid";
|
||||
updateBoardLayout();
|
||||
|
||||
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++) {
|
||||
|
||||
for (let row = -1; row < boardSize; row++) {
|
||||
for (let col = -1; col < boardSize; 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];
|
||||
} else if (row === -1) {
|
||||
cell.textContent = getColumnLabel(col);
|
||||
cell.classList.add("label");
|
||||
}
|
||||
|
||||
// 🔹 Zahlen (links)
|
||||
else if (col === -1) {
|
||||
cell.textContent = 8 - row;
|
||||
} else if (col === -1) {
|
||||
cell.textContent = boardSize - 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");
|
||||
}
|
||||
} else {
|
||||
cell.classList.add("cell", (row + col) % 2 === 0 ? "white" : "black");
|
||||
|
||||
if (board[row] === col) {
|
||||
cell.textContent = (mode === "queens") ? "♛" : "♜";
|
||||
cell.textContent = mode === "queens" ? "♛" : "♜";
|
||||
|
||||
if (conflicts.some(([r, c]) => r === row && c === col)) {
|
||||
// Highlight pieces that are part of at least one conflict pair.
|
||||
if (conflicts.some(([conflictRow, conflictCol]) => conflictRow === row && conflictCol === col)) {
|
||||
cell.classList.add("invalid");
|
||||
}
|
||||
}
|
||||
@@ -136,64 +170,325 @@ function drawBoard() {
|
||||
}
|
||||
|
||||
function placeQueen(row, col) {
|
||||
if (board[row] === col) {
|
||||
board[row] = -1;
|
||||
} else {
|
||||
board[row] = col;
|
||||
}
|
||||
board[row] = board[row] === col ? EMPTY_CELL : col;
|
||||
drawBoard();
|
||||
updateQueenList();
|
||||
}
|
||||
|
||||
async function solve() {
|
||||
function previewBoardSize(size) {
|
||||
pendingBoardSize = clampBoardSize(size);
|
||||
sidebarBoardSizeValue.textContent = pendingBoardSize;
|
||||
sidebarBoardSizeValueMirror.textContent = pendingBoardSize;
|
||||
}
|
||||
|
||||
if (mode === "rooks") {
|
||||
const res = await fetch("/solve_rooks");
|
||||
const solution = await res.json();
|
||||
function applyBoardSize() {
|
||||
boardSize = pendingBoardSize;
|
||||
board = createEmptyBoard(boardSize);
|
||||
updateBoardSizeDisplays(boardSize);
|
||||
setStatus(`Brettgroesse auf ${boardSize} x ${boardSize} gesetzt.`, "#2563eb");
|
||||
drawBoard();
|
||||
toggleSidebar(false);
|
||||
}
|
||||
|
||||
board = solution;
|
||||
drawBoard();
|
||||
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;
|
||||
}
|
||||
|
||||
// Damen (wie vorher)
|
||||
const res = await fetch("/solve");
|
||||
const solution = await res.json();
|
||||
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");
|
||||
|
||||
board = Array.isArray(solution[0]) ? solution[0] : solution;
|
||||
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) {
|
||||
board = data.board;
|
||||
boardSize = Array.isArray(data.board) ? clampBoardSize(data.board.length) : DEFAULT_BOARD_SIZE;
|
||||
pendingBoardSize = boardSize;
|
||||
mode = data.mode;
|
||||
|
||||
updateModeButtons();
|
||||
updateBoardSizeDisplays(boardSize);
|
||||
drawBoard();
|
||||
}
|
||||
|
||||
async function solve() {
|
||||
const endpoint = mode === "rooks" ? "/solve_rooks" : "/solve";
|
||||
const response = await fetch(`${endpoint}?size=${boardSize}`);
|
||||
const solution = await response.json();
|
||||
|
||||
// The queens endpoint may return multiple solutions; we display the first one.
|
||||
board = mode === "queens" && Array.isArray(solution[0]) ? solution[0] : solution;
|
||||
setStatus(`Loesung fuer ${boardSize} x ${boardSize} geladen.`, "green");
|
||||
drawBoard();
|
||||
}
|
||||
|
||||
function resetBoard() {
|
||||
const status = document.getElementById("statusMessage");
|
||||
board = Array(8).fill(-1);
|
||||
board = createEmptyBoard(boardSize);
|
||||
setStatus();
|
||||
drawBoard();
|
||||
updateQueenList();
|
||||
status.textContent = "";
|
||||
}
|
||||
|
||||
drawBoard();
|
||||
|
||||
function updateQueenList() {
|
||||
const list = document.getElementById("queenList");
|
||||
list.innerHTML = "";
|
||||
|
||||
const letters = ["A", "B", "C", "D", "E", "F", "G", "H"];
|
||||
queenList.innerHTML = "";
|
||||
|
||||
let count = 1;
|
||||
|
||||
for (let row = 0; row < 8; row++) {
|
||||
if (board[row] !== -1) {
|
||||
let col = board[row];
|
||||
|
||||
let pos = letters[col] + (8 - row);
|
||||
|
||||
const li = document.createElement("li");
|
||||
li.textContent = `${mode === "queens" ? "Dame" : "Turm"} ${count}: ${pos}`;
|
||||
|
||||
list.appendChild(li);
|
||||
count++;
|
||||
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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
drawBoard();
|
||||
|
||||
@@ -48,12 +48,15 @@
|
||||
<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 -->
|
||||
<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>
|
||||
@@ -62,17 +65,108 @@
|
||||
<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" onclick="openSavePanel()">💾 Save</button>
|
||||
<button class="btn" onclick="openSavePointsPanel()">📂 Load</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>📋 Damen Positionen</h3>
|
||||
<h3>📋 Figuren Positionen</h3>
|
||||
<ul id="queenList"></ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<aside class="sidebar" id="settingsSidebar" aria-hidden="true">
|
||||
<div class="sidebar-header">
|
||||
<h3>Brettgroesse</h3>
|
||||
<button class="btn secondary sidebar-close" onclick="toggleSidebar(false)" type="button">Schliessen</button>
|
||||
</div>
|
||||
|
||||
<p>Wähle eine Grösse zwischen 4 und 13.</p>
|
||||
|
||||
<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>
|
||||
|
||||
<button class="btn" onclick="applyBoardSize()" type="button">Apply Size</button>
|
||||
<p class="sidebar-hint">Das Brett wird beim Ändern zur Sicherheit zurückgesetzt.</p>
|
||||
</aside>
|
||||
|
||||
<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>
|
||||
|
||||
<p>Gib dem Spielstand einen klaren Namen und optional eine kurze Notiz.</p>
|
||||
|
||||
<label class="sidebar-label" for="saveNameInput">Name</label>
|
||||
<input class="sidebar-input" id="saveNameInput" type="text" maxlength="80" placeholder="z. B. Fast geloeste Dame 8x8">
|
||||
|
||||
<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>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
|
||||
@@ -5,3 +5,16 @@ CREATE TABLE users (
|
||||
email VARCHAR(120) NOT NULL UNIQUE,
|
||||
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
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user