From bc377b6e33ef7e7cf83ed2ea8ff48c441d8d9d87 Mon Sep 17 00:00:00 2001 From: ScriptSorcerer20 Date: Mon, 30 Mar 2026 19:03:13 +0200 Subject: [PATCH] improved javascript structure, added load selection and made chessboard selectable --- .../__pycache__/main_routes.cpython-311.pyc | Bin 5694 -> 9286 bytes .../__pycache__/main_routes.cpython-312.pyc | Bin 0 -> 8027 bytes app/routes/main_routes.py | 179 +++++--- app/static/css/dashboard.css | 140 ++++++- app/static/js/dashboard.js | 387 ++++++++++++------ app/templates/dashboard.html | 44 +- 6 files changed, 554 insertions(+), 196 deletions(-) create mode 100644 app/routes/__pycache__/main_routes.cpython-312.pyc diff --git a/app/routes/__pycache__/main_routes.cpython-311.pyc b/app/routes/__pycache__/main_routes.cpython-311.pyc index c3c7267c6a8196fe1cbc5349f6d8047d4668ab3b..c5a0bbff6a3d1f6c64b0d3e6c19412e50f45f3d0 100644 GIT binary patch literal 9286 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 diff --git a/app/routes/__pycache__/main_routes.cpython-312.pyc b/app/routes/__pycache__/main_routes.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..72826714a6570b1c1c9ef42e0cbed4c8cb908f78 GIT binary patch literal 8027 zcmdT}ZEPFKd7jx_el8_SBt=nQHuYtR`po$p-^Ed7`)r?my0hFwc6!IuU})}2rc8>; zE_JpPD&PeMEIDp1BbS;*{6h%>)P^0@Mf2l+6&Ijrfc6jRRVv;_?fK9vTK}UzfF?$P z_I+oyB&Cychx^qIu)~>s-_mPVYBdbnj_>U zk%&YSBy9ZBFoimo;F5fpPYPinX$f1B*07bLoKM)2_OLza2s@xINS1^%DTYN>wkBN3 zns5!2ZQ)wU9(GHPa2-cvUUJIyci~Hz!XCMHl1k!b8um)A%OvcRY9McrY9aT_p6Ovj zuR539cd1dA>MoPJo1$>w1={tdw(O8R75#F%rWdsHO1>$2ufZIt>kG77wvGp8(^_d&o=w{eTDO_sf_A%hXt(PH?RHnS3+~Wv_Y2y! zSGDWdp6NxJ;SwCQ+clItRhg^~l*>oP)#c_ImRz9XEDMfdF zD+mDeT*bBff!I&()*s~mjvnM=Wdne~0uM9KTTr+{uFxyo75<6p5n8u`JS(qRp%lM=JfwTn-R``e) zo~IeeeoSxf2!^BN>>weF+_I42nm})n&+r*~p_dSaqhy|@al9s% z*jlC62C=U=Df&|R~N{BAror-Gp_R@G4E z29J?vBC{zqo{pzxR2A0{j2z4=tx(eaaW%44C89M6*6mn9pm?W>(X8GAx(j<%(@iY-Z2i>jSs7XF zEO@&$_nv#5w-X;s6ow9IeTOyoVGx(x(6r(<<~mFUIgs*2#2EVh79ax1%Co6Q{V$6WK%j()P-3KW?*chi@Xy4gRRo(T0ni_!&j8hbASNa}^twmSMx~rq$>R3Ja zH!tNozq;nyUvzms^#*dynz!o-5gdI>M?P|Quf}e_{2TXhv8Qjvy~-Erx<0CF$sJj3 z|I6cRbzQ}t5Wam-*Y(%|4U`akLqTr41O8N*8yhE^lQM)uK>&W|5F`VDuIdRWUjbn= zYVC#WzhS_QO)F9^T=4f~PZj-v?5Ssh)8hNDfWy*PB9J}y6HDC+y*yqNoAW|58^dP+ z!zklz=&;&_DtT{Ede|>p@xh-K@=oF*u{iITun1G>sIhpDV5Z1 z>iN0J$%Gu@*ox@2N-A}s${lgNf`L}MvXLsj;J;A`-x2p4dxhNfHHt4^)CUuzi1 zj&BIgE938tuZ*q<9YwJzFEo92>bNzrssAe!s;7=o-u9Fs)8);mA~UDCNI|~iLcn|c z&bE{;Z-$knO5>i(l0+}VvK=i)Zsu2@uUb>5@;i--yDdp&zm@n~R^V@8e2dj_+AqWN zbO5q935Drr$XVE1U?LFCUU&u8m0oj5^e^a5O3Afaq}gBa7ioxRPOJ1oyKx0$Pw-!E z=qtJv$aF3_qgL7LxnpEXo{=xjDx(WM5QfB~3D{aAQxI-MRE#$&v{_a>4n3-V$o>=E z@j>g6)&2J;Kji*4aqAm})+4`HaV>w~^wB@F`RGD@_dA2#$-!UUg?>CahKU+;!hh6IO6~2jNlALI$99)mJ)!yEcwA$1Sxdxbu`;+nr^7M$P&uL58R7#_s7Ab0c38j)H@@JE zLu7er=zKbv2vG%no!Q!;uvVz*!ueEER>q)+v4)D+&5}>W=G=%T_U46Nrn=x9mZIu; zW{oWaL04h}>4L)6fmEWud*$d52E~sr)Rz0&9GEU(bPLSE28jH>eUy%J6Xhg`zX6Zx zfhK3%c;frzTEAY-~%)>GLT`-7-!9tH4zq83R~<`LNuXu5gtfG1Y>KAqUq?iesw2fga}fEE}Ew$j1S8d3=c&W z*~;KHy@pk!cyuZ?1JNF?Y0yl@4rO8RQN9h5KY>U6HDu@(p6esaBiW-xSKUhE^2zM6 zqO*2sZn-~uq*ADFxEZ(+$n9C})$04RW1n^n-j=nleNTvJ87$a?OXAA6i=sc*0ny2- zcw2oC+*@?{9{Sr?Ckp=FC2=Frl6&)ZXQ6da3k?0T^Ztboj}-PF*Y=($1Wqi~d?NZc zyv@1et7ic6ns?xVYv7^SkPB#H`_GcwC8b9A`!Odqwk_mE$8`lLqya6uo(8a)^3sGfC zRVJ{GXkY=<9m+NZ6=Rgm(85z4Wf|u=B?{scJSuK>V8#0OpZEOLz)uI(JN6bj_TJyG zbsW&@zn&f25IygYKUSYC_F%8U zj1SAqSl_TRd7~8wQ1orowX*Q1)^mM&c{?+*#-s z4s&J5RRDj3yXF=^W3fEy38+!_<|4+3K%z2yCgfz;(Jin;EAzTzGM<3lH*#*i3QhKu zg7=i?=3yt6)ffONkucc!IQGEdE(?HU7*Uni;oX){x(-i`aO+aGn({8FTt_veXuy%3 zI6T+g%kFi1d%@m*Z{V+o?+oYd?VA0-$Bz2t!26+_12+cNJ)H$lr{?L_9D6=?xR$*? zY$(~_bPPapc|ZK#KYJ%WXkPR6-fsSlZ{Gv&geIQO3#We%B^4RuS1);9bJ`ay!+1*A z!dB(&53g4~FC6^;2Ad3=#wyT}xG9>(_?acVPLy~_xK%!EPE^6H!c^0Ep4*n-D2r#1 zZOMc(Nb!)?fH4sxz*>XSW>Ev#rUFMr!KwA?bS3gv& zYgw-g7V3hJExgbAl<+QFiSQPi!GK;Ryp@SDU8cVZiJ5qUgN0=r;K7dd$XdEZ#y<)c zd=tPbLADnJXHx0lWNL0kioXv=4cT7|pi9uh7XGqrE+d=}U^WI_H5c+}I7vHh@N*UeKmPObX}3%)~Gw=Y4~k@0;tsw+p_v zHD5S;@)I!taLC#6?XTn;U)9`S`{3ogdo*t!1u(36dv4cg-u;^RN?v${89dZ(0QqJ3 z2tYm&RWB&G&lyudV;iBe$V3tLUyp1Dpg-&O zNMr_2Oc6Lb<3A(kW{p@J?=9Jt9lHX7n|l2Bgk2x7gE>1fv4C4aZe@W2V|fKVNy%b1 zj2XK_#S4h%_7ND+TtXgImY|AmsQx+R@HYviza`>tNzH$dQH_j#O!oadss9(!`>?^E zC!S)xCtF*r_hxH9ae7x?&z;bm-B}x4y7V4+O04t{ed1}QEl);y+Wf@jq}Eb1;T%`m z-fLS@HR^rLQ5t&66V6w{Qps)#Ga79zQa8#EQB?L{p% zkiL=t)lye=KTOF+&s2uHyCh(}ly`S&w3}&J>8oX} LJ&y&bGJE|maJxr~ literal 0 HcmV?d00001 diff --git a/app/routes/main_routes.py b/app/routes/main_routes.py index 5222b6f..cf8405c 100644 --- a/app/routes/main_routes.py +++ b/app/routes/main_routes.py @@ -1,20 +1,88 @@ +import json +import random + from flask import Blueprint, jsonify, render_template, request -from flask_login import login_required, current_user -import random, json -from ..models.gamestate import GameState -from ..models.user import 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__) + +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 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": len(board), + "pieces_placed": sum(1 for column in board if column != -1), + "created_at": game_state.created_at.strftime("%Y-%m-%d %H:%M:%S") + } + + @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(): @@ -22,71 +90,48 @@ 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"] - mode = data["mode"] + piece_mode = data["mode"] game = GameState( user_id=current_user.id, board=json.dumps(board), - mode=mode + mode=piece_mode ) db.session.add(game) @@ -94,15 +139,43 @@ def save_game(): return jsonify({"status": "saved"}) -@main_bp.route("/load") -def load_game(): - game = GameState.query.filter_by(user_id=current_user.id)\ - .order_by(GameState.id.desc()).first() - if not game: - return jsonify({"board": [-1]*8, "mode": "queens"}) +@main_bp.route("/save_points") +@login_required +def list_save_points(): + saved_games = ( + GameState.query.filter_by(user_id=current_user.id) + .order_by(GameState.id.desc()) + .all() + ) + + return jsonify([serialize_game_state(game_state) for game_state in saved_games]) + + +@main_bp.route("/load") +@login_required +def load_game(): + latest_game = ( + GameState.query.filter_by(user_id=current_user.id) + .order_by(GameState.id.desc()) + .first() + ) + + if not latest_game: + return jsonify({"board": [-1] * DEFAULT_BOARD_SIZE, "mode": "queens"}) return jsonify({ - "board": json.loads(game.board), - "mode": game.mode + "board": json.loads(latest_game.board), + "mode": latest_game.mode }) + + +@main_bp.route("/load/") +@login_required +def load_specific_game(game_id): + selected_game = GameState.query.filter_by(id=game_id, user_id=current_user.id).first() + + if not selected_game: + return jsonify({"error": "Save point not found"}), 404 + + return jsonify(serialize_game_state(selected_game)) diff --git a/app/static/css/dashboard.css b/app/static/css/dashboard.css index 19f7438..ee0e1b3 100644 --- a/app/static/css/dashboard.css +++ b/app/static/css/dashboard.css @@ -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,119 @@ body { .btn.active { background: #22c55e; -} \ No newline at end of file +} + +.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); +} + +.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-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-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; +} + +.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; + } +} diff --git a/app/static/js/dashboard.js b/app/static/js/dashboard.js index c8c744a..abb2334 100644 --- a/app/static/js/dashboard.js +++ b/app/static/js/dashboard.js @@ -1,53 +1,95 @@ -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 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"); + +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 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 +98,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,65 +155,163 @@ 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) { + toggleSavePointsPanel(false); + } + + settingsSidebar.classList.toggle("open", shouldOpen); + sidebarBackdrop.classList.toggle("visible", shouldOpen); + settingsSidebar.setAttribute("aria-hidden", String(!shouldOpen)); +} + +function toggleSavePointsPanel(forceOpen) { + const shouldOpen = typeof forceOpen === "boolean" ? forceOpen : !savePointsSidebar.classList.contains("open"); + + 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 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 renderSavePoints(savePoints) { + savePointsList.innerHTML = ""; + + if (savePoints.length === 0) { + savePointsList.innerHTML = ''; 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 boardMeta = document.createElement("p"); + const preview = document.createElement("p"); + const loadButton = document.createElement("button"); - board = Array.isArray(solution[0]) ? solution[0] : solution; + item.className = "save-point-card"; + title.textContent = `Save Point #${savePoint.id}`; + createdAt.className = "save-point-meta"; + createdAt.textContent = `Gespeichert: ${savePoint.created_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)}`; + + loadButton.className = "btn"; + loadButton.type = "button"; + loadButton.textContent = "Diesen laden"; + loadButton.onclick = () => loadSavePoint(savePoint.id); + + item.appendChild(title); + item.appendChild(createdAt); + item.appendChild(boardMeta); + item.appendChild(preview); + item.appendChild(loadButton); + savePointsList.appendChild(item); + } +} + +async function refreshSavePoints() { + const response = await fetch("/save_points"); + const savePoints = await response.json(); + renderSavePoints(savePoints); +} + +async function openSavePointsPanel() { + await refreshSavePoints(); + toggleSidebar(false); + 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++; } } @@ -205,30 +322,38 @@ async function saveGame() { "Content-Type": "application/json" }, body: JSON.stringify({ - board: board, - mode: mode + board, + mode, + boardSize }) }); + await refreshSavePoints(); alert("💾 Spiel gespeichert!"); } async function loadGame() { - const res = await fetch("/load"); - const data = await res.json(); + const response = await fetch("/load"); + const data = await response.json(); + applyLoadedGame(data); +} - board = data.board; - mode = data.mode; +async function loadSavePoint(savePointId) { + const response = await fetch(`/load/${savePointId}`); - // Buttons richtig setzen - 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"); + if (!response.ok) { + setStatus("❌ Dieser Speicherstand konnte nicht geladen werden.", "red"); + return; } - drawBoard(); + const data = await response.json(); + + applyLoadedGame(data); + setStatus(`Speicherstand #${savePointId} geladen.`, "green"); + toggleSavePointsPanel(false); } + +pendingBoardSize = boardSize; +updateModeButtons(); +updateBoardSizeDisplays(boardSize); +drawBoard(); diff --git a/app/templates/dashboard.html b/app/templates/dashboard.html index dcf526a..9c988c1 100644 --- a/app/templates/dashboard.html +++ b/app/templates/dashboard.html @@ -48,12 +48,15 @@
+
-

♟️ 8-Damen Spiel

+

♟️ Damen- und Tuerme-Spiel

+

Aktuelle Brettgroesse: 8 x 8

@@ -64,18 +67,51 @@ - +
-

📋 Damen Positionen

+

📋 Figuren Positionen

    + + + + + + + - \ No newline at end of file +