From 02cb1d0a640a88fa7927e398a001cad11c13e6b1 Mon Sep 17 00:00:00 2001 From: ScriptSorcerer20 Date: Wed, 1 Apr 2026 11:54:40 +0200 Subject: [PATCH] added step wise solution and an animation. --- __pycache__/run.cpython-312.pyc | Bin 0 -> 275 bytes app/__init__.py | 5 +- app/__pycache__/__init__.cpython-311.pyc | Bin 2477 -> 2477 bytes app/__pycache__/__init__.cpython-312.pyc | Bin 4439 -> 2108 bytes .../__pycache__/gamestate.cpython-311.pyc | Bin 1747 -> 1747 bytes .../__pycache__/gamestate.cpython-312.pyc | Bin 1743 -> 1743 bytes app/models/__pycache__/user.cpython-311.pyc | Bin 1475 -> 1475 bytes .../__pycache__/login_routes.cpython-311.pyc | Bin 3515 -> 3515 bytes .../__pycache__/main_routes.cpython-311.pyc | Bin 13583 -> 17527 bytes .../__pycache__/main_routes.cpython-312.pyc | Bin 11962 -> 15316 bytes app/routes/main_routes.py | 121 ++++- app/static/css/dashboard.css | 133 ++++++ app/static/js/dashboard.js | 432 +++++++++++++++++- app/templates/dashboard.html | 25 +- 14 files changed, 689 insertions(+), 27 deletions(-) create mode 100644 __pycache__/run.cpython-312.pyc diff --git a/__pycache__/run.cpython-312.pyc b/__pycache__/run.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b970ad63162bc8b0a8b0a76f84b7232f8a14f530 GIT binary patch literal 275 zcmX@j%ge<81pJTAWTpe@#~=<2Fhd!i{eX<=3@HpLj5!Rsj8Tk?3``8EjH{v2Q4Fa} zDNNZK3=BoWsSH`{FzpCBg?SCjYDNg3k)e{cl2w!KC5WrZc#A8!C^fMpH9oPR;1)-G zd~RZ9UVMBAP=YljHK{b+Pm|>qGf=JwlIY~ z;;_lhPbtkwwJYKQ3WMw`)&deAm>C%v?=na|WD{u4X<&Z9%-vCSiCOXn3wyhJqx%Gv S2KTEh;vbk9Sfq+TVF3UJPd&{5 literal 0 HcmV?d00001 diff --git a/app/__init__.py b/app/__init__.py index ddb86b3..621c801 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,7 +1,8 @@ -from flask import Flask import os -from flask_sqlalchemy import SQLAlchemy + +from flask import Flask from flask_login import LoginManager +from flask_sqlalchemy import SQLAlchemy db = SQLAlchemy() login_manager = LoginManager() diff --git a/app/__pycache__/__init__.cpython-311.pyc b/app/__pycache__/__init__.cpython-311.pyc index c75a2cb53ecd25c07bde4182f6d7f95ab4362cfc..f13f5cffc8dfe2f8c316f537ff1ca170f21840ce 100644 GIT binary patch delta 257 zcmZ20yjGZZIWI340}#A;aVGQDL|#?CD25b<6s8=;T&5@{Muv$F(xPn3fby$BiohU> zorxipWnzK25o0ieCfiGppr0n=EmpUj#NuoqgU2U7Ju}ZYF)uMawFt=P3J&yf%t_8j z&8^(Hb1e&_?B;aVDU6IbLf mM2I<)|8m&dayEeB0|xO6sOScR$^}$(gF*cQDw zorxipZDN7A5mPXOCfiGppeExjR=1qQ;%q-4iz_(L$1x{4BQ>`Y$m8+JPtVNrP0UM7 zPc7QGb1e&_%;t2~DU6JqoByz#VKlwPng-HX#0)g1ND@fg5&#Rt7Z>IvLQE-Q0SR+M ogmXZ~P5#SaZ!3O*0R`P);A{ZH8w~0fP|*zrl?$k7awVq;01UrM-2eap diff --git a/app/__pycache__/__init__.cpython-312.pyc b/app/__pycache__/__init__.cpython-312.pyc index ae97128424de79a8757e3b913bbd82e78df38ceb..e7768a0ce75829f99a5bab57a2bc7ad0b1896dbc 100644 GIT binary patch delta 692 zcmYLGO=uHA7@gU_WRvVB+9qimE3Fn~g+{BTCs89-FIBLmmql3C>>5{RH*sbI1}zQf zMG!>h;8E}<9u-d>MDe6Yp}5+E2cgAFp@NW$&bI;m4sYLkGxNTW{WcD~DVZM)BZow7 zZG8$7`LQ{+zAV%e7XeHXFx4Lv-A?DI;{;IaH0Y?_R@AY_E|l2gUA_jTIG9+Rs#a)C;(ifRe*H7j9^n~ouMOw{}1kT=&Uec zr|b{ncZ$zOusZfFn|owDFdi0v%R>Fq_(JLIdQH$BBw)ax(fecN#j^aNtS;`)SUEZl zvFbHg6_tVKQ|=Jh8CAe34IOrixWlDURoK)7mL!}mM2Ekt#d}x!(wEXcZuX5`WfB+Dn7=qJwT}P* literal 4439 zcmbstTWk~A^^QH`*Eq2WB%uUiCnSUvNWvpLv$Q5Lkast6w!|zNnamLE9oyNNNkS}p zFR^898REb4Z)m8fw*J0z{4Qi^gAN$9Wt+wT>=Z<|nJe5iv z$@iXn&->iRIoE$FDRCh9(!cq0cm<$;k^%ip1z>dyfNMxZA|sJ!p?Qf({0mki^|7hz;^EvKB{9(Q+9DOGGQQrBkLx z+n0o{k-^+?Ii$YT2asd%^&X!*a$br~!N}PY9}UNPL$T1Pq%@kaH5^mNq!A6Xnsh-M zDQM9<3$$kVt-cHGHKZ{VsZk$Mgr##vf2Tl~0?(5uVl^-nqSj9^3NwX(#!fLtFEUh4 z2byC>Oy|*v=?ut9F-b-=eG3SGY?`&RC}m19qFH1`PPB+t(RPzdnUm%SZ%CpL)!Kp1 z>_O;F@FB&fxFnl2O)x4m%Rl9nlkymS#AUz~($-NZ$x&-*&!E|TELub6ddhF~)JoU| zcF`d_qI}B2prj?T(V(GFq>4gDPx({Uq!m293trl^EfgQArjXGmZ4s|Aq7d~XW!JXS zxui|xMRbSk3HVgER-cCuO4=!X(k2wWdWO^a6lW9Y+)jCoKC;7rDMbCbfy5Hv+DYe) z{s#KF8MIc?8K7#Ru+gWjVkuA+qxg(%F2gpUa|0dOVEXqA-8zHr5;OZ-(6o=z89i_p z*JbwJ|F;LGVAnJ7OEHtEv8)d}Mnh3aP_>XIVKxSE;)Jg!;2-b?e8+nHUIXj(b#!>! z`+J6Z`@Co4Aw?9_a6G5{>kNbvt0^Wm(@2@RL4NXV_(dvD} zH_(34H?aL+OXG{O8irzoNc0W@eM6$-e-#|pByYff3M_lxx`u|t$+e|~RpDG{BCdo% z&awXf9>1^eMNut@Q)^2a9}^+Mq7c%&9ln4+(An#MQBwQRz<|FmAQ0?ez}Ne|Mh-iS zSYvxJToeQcG5?g6*kLI8lH8gfGT43sLN`bjSm9wtAy-ktqKDFpkx~l7;|jTp8aFHC zg2pECEVf87xYQ-=8kICb3!Rno%cNqSOpL_k@n}rN90Vq+Vyh~tYB(OlR_TH?G7gH! z6^$(;@n|%xVRtkxO0wES_L9EQlrn6j&wFsFqzS;D5UsQaRnfHd6=>=;p`LYVU&Ik3Hujp7QihnS7s(uJ_9g=3>d zKOE8Us1nk^1#mD=rSD1zGl^SNifMV9dE-2&3du1_4Kymr-QME$GFYZioXEUXDa z)7TWQlw#_*A_>I^2r9{*A%$EkWIfb5SgbT!c30js-LhY|XWg~w&K2wW`9n)p`!ZGg zvQ_&t*8MqK=}PO-rPi)YYuBf3+168^RSw_YKW~|zxKX-LIlRDbfOe%q$d$)(<)?F% zpEQ&yiX zYe;vmRMb31Ztn23W7)mon&Umk{Nbgl9hs^fS@+Iq?xCyVYU2I2k2Wm2_Al2p+~z*$ zn(kV3*5v1pe8eugT65c4eii>kd}-T}%(f%hZLclVAN}3APlA6sbMMS2{R>+Igu%PQ zmoM?24DVUs8**Fg^|^<9+111IsoP?f-@Cvc$<=?GOxNb8leYs|eoux!oU3bEsymda zJ2c&O-&ymh25kMh?ittavz7mO+|8hkK}IEq;U01xZ(~OQDt^Tx<}*n6S1d0tV`%b9 zA+1=@LH8{v2e(uwXMlJ&ViLTiGoP)Q2^p7xXe-eCdKnD3Zx&@T)PbS6qgX=0Zu z!S$={dP|yu7lOZiz#kC0{lmv`^;2Ad0bhH!(A(e9dAzgT7wGKo8%*#rc|bJjtwQ!0 zJSx~BRZ=9j2Y9FTQU1^{^oekvR$vJ%S<*H3LfYEHM4C7GO= zoVz%4@uqTX^7`bhi`Osy`a;%oFzY&$KDn~<;P3a`J@D~?yKNu0eQH}62xgC*&g?v$ zZYSAs!L>cZHs(AahYY8a8Fq7S??H-e&af>HS<98~OWjw^i|mGFcHI)YDZ_4>-@C|q za-N-+dl%WwU$8X~Ai3I|bDkMb*1qX0$knyWHpg7aOv$Wk+2&ca)qDd5jM+Ewp1#wM82x~1UIJn5x<%**Ni|p1FzBJwS%@&aJh$wiXlB@C$ zEgrwe{6`Px-)?PmVeTAx@$1`7`3|5J4>Z`V&kArog!2CZC?vySK0lp5#pK*!a)VG3 za&YykFn_X6kRd64^p*G!DMtuQPSO)w8*I37Svsnmf+0ElDj|GiW*FuHs{9<;KS%rn n)bIe+{T|i#o2AOKcrAz!T6P(*X diff --git a/app/models/__pycache__/gamestate.cpython-311.pyc b/app/models/__pycache__/gamestate.cpython-311.pyc index 9a1f79a2f5a8535fcb9975de9cf305e3db026cce..15f2556c1dd6a1c22e048fbd7d8d72b698dd5ef1 100644 GIT binary patch delta 22 ccmcc2dzqJaIWI340}x0*I+JO*k@pxI07zQ~h5!Hn delta 22 ccmcc2dzqJaIWI340}zy~J(Vf5k@pxI07+d2lK=n! diff --git a/app/models/__pycache__/gamestate.cpython-312.pyc b/app/models/__pycache__/gamestate.cpython-312.pyc index db13518392aa5ee34fb3b8da4780cd331dbc795d..7606bb26f25e6ca489169a9434b2f42cdcf3b7cc 100644 GIT binary patch delta 22 ccmX@ld!Co~G%qg~0}x0*I+JO*k@pZA07?%Ao&W#< delta 22 ccmX@ld!Co~G%qg~0}zy~J(Vf5k@pZA080@Ds{jB1 diff --git a/app/models/__pycache__/user.cpython-311.pyc b/app/models/__pycache__/user.cpython-311.pyc index 0748aa0ac9ad887b021489ae3b80c092f71d8fda..1ecb09acf640c7fe0cbdd9e7b05c761acc9be631 100644 GIT binary patch delta 20 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 delta 3292 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 DZfkK9 delta 3140 zcma)8TWlOx8J@Fq*}D_39bfhmuN~X#u^p>PjO(-^p%6QD+PF&W1Z8O0jCW@3NoIGJ zGwZmCT5M1%DtPGSv}y{yq*S6xourjq@c;_tfv5H*&?2F!Kp^n|ybzK+01uS^KjU3H z#*r9lfB)zF-}%pfKmKgtXQvuIiN_-XeD7ZS*!pGi#fG-U+W|p%RWJmD*n;N&B^rTs zzz&o|O)N>8RFXBhq-Y93xo8JVAuUu2YhhSRhHOVlQ7y`O#g3KYS{(SG)?kFREk;<| z8W2p;h?tGba1(x7!fYrKBYKf&O@a-eS=Qr!gEeV(ZOf5kLSuDmDSAaVxSMrO-@=|}_THC1%#n)#xB&Y@Nn7+asE zy&y?4S5a4v2Sw7Mij(8hYQU4JUY<2QQJ-;D*%PU=;E4stW(%QFl4c)-Cgd(S=(7KW zrb&(+4K}mc@Oz^w?7?A0uT;#kK?iUdq&wjc9YT^qvKxpOt=M|O%zta%G|O}sn}~EO zXd@kAl}IlcVy{N#CUUreg?dF$<+ZbP1ow9#c@T&z13CQ__$c=><~sJdRn0El3&QQ} z(dfh6%69ff^i*#n?5UC`*_K=7HhKzpYd9q@=+4a-i?*o-Xo_WHsaCEr>T9HqbJnE? z;P98(vDhHVtqgDbh{VumIt*lzjrlAia%Mau?T@-ydY5Vn2K6a+{*opn8N)~r&jL|$sX zSCV161t>1Jz}@nl4mlKJ1bS43&fxjDYuaX^Y8rVzdZ^>;C}6+MbU~Q^nMunwtfyDn zx1S`CV7KG&2v!3fMZ(L6Cs3MmAb<{R;?Io(SrYynO5yNVD?-AoO9IQUugz)k4@&8@tXeU6;wIbN%K9_Mes35wO? z$>$(P2K1RiF;H|8iHVZ1S)K<|F}Z`dDFIQ#o@D4%ou_-Xnb@;k(~aCWxiDuI&eCrK z@B;f=*G&k+&vz{M^MG^Z2I@iOTp4c&`b}86INPCa*bCil@^^uMiCya+hU1CepB!0& zHCT6FE!YM3Xo0oYxUNs~)R_k-F2epL(3@j}xj5){`dTAl*c0J*=c$z!`W8r_nqYmY zG&|BCB`xe^e{aLp$TDUDzGs5fHugk+y6X&Dvf+C=Seu7L@L_>79qiY;Gp(I9q!F#| zsPRt+S?55~;+esGcwwH=DQFfb?YFm3H?Q%gyjV}I|b|2U8ib#5z8H49iA}O zP5{Qw2~Y3FK4#YkIunU?3Fg5x*`Ee7ynoiZ#gmQsQpKhC-vEpt({(MU4BcTEo>Fj1 zC9CSE4*OUQ(WmP7o=0xuy|{M_h#LZuUH$y=5JvxCGQ`2dS3vqE!X)<2;Op$Moju{Z zTS5tC>G0iVwzzXYBsJT zC(0NCPcZsG-w7NSyrpRYIZQ4e6P{eOC=58R69)_r0euO%myw{yR{~y;4>QZ~{i@%1 z>w#?K$AI`1{M>mKA020>ckO}M_VTU;viKuZ@H&z=kf5*h2SB`_0b>QGiW;T&GmO54 zXl@oJgW%dmaV=xy|+c^=}5W5vr=`YwQegFrOy5c|WP-C$hf@b`&v9CrCE zpg0xaxv7_>8oR$$b9f4$1zJQ>H_}TS92gKvDf4k-%io%Ruh&JHd6k6|-O!t%85aH30ox zVBygx7jwX>J^qB-2Rx+Q*4?ud2Y?@7K6P?2_KO5dnD?UJ8(Z-mu-zdn z2mb}+W?w;63z9Ze6Uyhy_(tZvU};ge%K4efy7veFA>fZVFD_o#`1Ikh!pDIRcs@-j z&O|=Ic&|{bZHnc_)5-%$(a{Z$&A&tYz{+{s+)qCgglz&&S@$l`TOuLkb0PY<5NEIL z-5*Jl)b0HuN#2gJ+k4+)*B@%`es1A%%jM8g=2N2nj%@iyAW4FEL?N&hN&hVgmbV^a L^247<7}fs*K4HV5 diff --git a/app/routes/main_routes.py b/app/routes/main_routes.py index c1b77b2..15ea363 100644 --- a/app/routes/main_routes.py +++ b/app/routes/main_routes.py @@ -1,5 +1,6 @@ import json import random +from datetime import datetime from flask import Blueprint, jsonify, render_template, request from flask_login import current_user, login_required @@ -27,6 +28,10 @@ def parse_favorite_flag(raw_value): return str(raw_value).lower() in {"1", "true", "yes", "on"} +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 = [] @@ -64,6 +69,105 @@ def solve_rooks(board_size): 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) @@ -149,12 +253,24 @@ def solve_queens_route(): return jsonify(solve_queens(board_size)) +@main_bp.route("/solve_trace") +def solve_queens_trace_route(): + board_size = parse_board_size(request.args.get("size")) + return jsonify(build_queens_trace(board_size)) + + @main_bp.route("/solve_rooks") 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(): @@ -166,6 +282,7 @@ def save_game(): 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, @@ -174,7 +291,9 @@ def save_game(): board_size=board_size, save_name=save_name, save_note=save_note, - is_favorite=is_favorite + is_favorite=is_favorite, + created_at=timestamp, + updated_at=timestamp ) db.session.add(game_state) diff --git a/app/static/css/dashboard.css b/app/static/css/dashboard.css index c3ca90f..bf902fd 100644 --- a/app/static/css/dashboard.css +++ b/app/static/css/dashboard.css @@ -44,6 +44,12 @@ body { text-align: center; } +#statusMessage { + min-height: 24px; + margin: 0 0 8px; + font-weight: bold; +} + .board { overflow-x: auto; padding-bottom: 8px; @@ -91,6 +97,21 @@ body { 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 { background: #e2e8f0; } @@ -107,6 +128,10 @@ body { /* 🎛️ Buttons */ .controls { margin-top: 15px; + display: flex; + justify-content: center; + flex-wrap: wrap; + gap: 10px; } .btn { @@ -123,6 +148,12 @@ body { background: #2563eb; } +.btn:disabled, +.btn:disabled:hover { + background: #94a3b8; + cursor: not-allowed; +} + .btn.secondary { background: #64748b; } @@ -163,6 +194,79 @@ body { 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; @@ -175,10 +279,14 @@ body { 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; } @@ -329,6 +437,20 @@ body { 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; @@ -337,4 +459,15 @@ body { .navbar { padding: 15px 18px; } + + .solver-panel-header, + .solver-speed { + grid-template-columns: 1fr; + display: grid; + } + + .solver-step-info, + .solver-speed-value { + justify-self: start; + } } diff --git a/app/static/js/dashboard.js b/app/static/js/dashboard.js index 437d712..ca8b30c 100644 --- a/app/static/js/dashboard.js +++ b/app/static/js/dashboard.js @@ -2,17 +2,34 @@ 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"); @@ -63,6 +80,21 @@ function setStatus(message = "", color = "") { statusMessage.style.color = color; } +function setSolverMessage(message = DEFAULT_SOLVER_MESSAGE) { + solverMessage.textContent = message; +} + +function updateSolverSpeed(rawValue) { + solverSpeedMs = Number(rawValue); + solverSpeedInput.value = String(solverSpeedMs); + solverSpeedValue.textContent = `${solverSpeedMs} ms`; + + if (isSolverPlaying()) { + stopSolverPlayback(false); + scheduleNextSolverPlaybackStep(); + } +} + function updateBoardLayout() { const cellSize = Math.max(32, Math.min(60, Math.floor(480 / boardSize))); @@ -70,10 +102,85 @@ function updateBoardLayout() { boardDiv.style.setProperty("--cell-size", `${cellSize}px`); } +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; - resetBoard(); updateModeButtons(); + resetBoard({ + statusMessageText: `${getPieceLabel(mode)}-Modus aktiviert.`, + statusColor: "#2563eb", + solverMessageText: `${getPieceLabel(mode)}-Solver bereit. ${DEFAULT_SOLVER_MESSAGE}` + }); } function closeAllPanels() { @@ -99,8 +206,6 @@ function getConflicts() { 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) { @@ -116,16 +221,16 @@ function checkSolution() { const allPlaced = board.every((col) => col !== EMPTY_CELL); if (!allPlaced) { - setStatus("❗ Noch nicht alle Figuren platziert.", "orange"); + setStatus("Noch nicht alle Figuren platziert.", "#d97706"); return; } if (getConflicts().length > 0) { - setStatus("❌ Es gibt Konflikte!", "red"); + setStatus("Es gibt Konflikte auf dem Brett.", "red"); return; } - setStatus("🎉 Perfekt geloest!", "green"); + setStatus("Perfekt geloest.", "green"); } function drawBoard() { @@ -134,6 +239,7 @@ function drawBoard() { updateBoardLayout(); const conflicts = getConflicts(); + const highlight = currentSolverHighlight; for (let row = -1; row < boardSize; row++) { for (let col = -1; col < boardSize; col++) { @@ -150,10 +256,19 @@ function drawBoard() { } 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" ? "♛" : "♜"; - // Highlight pieces that are part of at least one conflict pair. if (conflicts.some(([conflictRow, conflictCol]) => conflictRow === row && conflictCol === col)) { cell.classList.add("invalid"); } @@ -170,6 +285,18 @@ function drawBoard() { } 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(); } @@ -182,7 +309,9 @@ function previewBoardSize(size) { 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(); @@ -358,30 +487,284 @@ async function openSavePointsPanel() { } function applyLoadedGame(data) { - board = data.board; + discardSolverTrace(`${getPieceLabel(data.mode || "queens")}-Solver bereit. ${DEFAULT_SOLVER_MESSAGE}`); + + board = Array.isArray(data.board) ? data.board.slice() : createEmptyBoard(DEFAULT_BOARD_SIZE); boardSize = Array.isArray(data.board) ? clampBoardSize(data.board.length) : DEFAULT_BOARD_SIZE; pendingBoardSize = boardSize; - mode = data.mode; + mode = data.mode === "rooks" ? "rooks" : "queens"; + currentSolverHighlight = null; 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(); +function applySolverStep(step) { + board = Array.isArray(step.board) ? step.board.slice() : createEmptyBoard(boardSize); - // 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"); + 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 resetBoard() { +function advanceSolverStep() { + if (!hasPreparedSolverTrace() || hasCompletedSolverTrace()) { + stopSolverPlayback(false); + updateSolverControls(); + return false; + } + + solverStepIndex += 1; + applySolverStep(solverSteps[solverStepIndex]); + + if (hasCompletedSolverTrace()) { + stopSolverPlayback(false); + setStatus(`Loesung fuer ${boardSize} x ${boardSize} Schritt fuer Schritt aufgebaut.`, "green"); + updateSolverControls(); + } + + return true; +} + +function scheduleNextSolverPlaybackStep() { + stopSolverPlayback(false); + + solverTimerId = window.setTimeout(() => { + solverTimerId = null; + + const advanced = advanceSolverStep(); + + if (advanced && !hasCompletedSolverTrace()) { + scheduleNextSolverPlaybackStep(); + return; + } + + updateSolverControls(); + }, solverSpeedMs); + + updateSolverControls(); +} + +function startSolverPlayback(stepImmediately = true) { + if (!hasPreparedSolverTrace() || hasCompletedSolverTrace()) { + updateSolverControls(); + return; + } + + if (stepImmediately) { + const advanced = advanceSolverStep(); + + if (!advanced || hasCompletedSolverTrace()) { + updateSolverControls(); + return; + } + } + + scheduleNextSolverPlaybackStep(); +} + +async function prepareSolutionTrace() { + const endpoint = mode === "rooks" ? "/solve_rooks_trace" : "/solve_trace"; + const requestId = ++solverRequestId; + + solverLoading = true; + stopSolverPlayback(false); + solverSteps = []; + solverStepIndex = -1; + currentSolverHighlight = null; + setSolverMessage(`Berechne ${getPieceLabel(mode)}-Ablauf fuer ${boardSize} x ${boardSize}...`); + setStatus(`Solver berechnet die Schritte fuer ${boardSize} x ${boardSize}.`, "#2563eb"); + updateSolverControls(); + + try { + const response = await fetch(`${endpoint}?size=${boardSize}`); + + if (!response.ok) { + throw new Error("trace request failed"); + } + + const trace = await response.json(); + + if (requestId !== solverRequestId) { + return false; + } + + solverSteps = Array.isArray(trace.steps) ? trace.steps : []; + solverStepIndex = -1; + board = Array.isArray(trace.initial_board) ? trace.initial_board.slice() : createEmptyBoard(boardSize); + currentSolverHighlight = null; + + if (!trace.solved || solverSteps.length === 0) { + setSolverMessage("Keine Loesung gefunden."); + setStatus("Keine Loesung fuer dieses Brett gefunden.", "red"); + drawBoard(); + return false; + } + + setSolverMessage( + mode === "queens" + ? `Ablauf bereit mit ${solverSteps.length} Schritten. Nutze Next Step oder Play Animation, um das Backtracking zu verfolgen.` + : `Ablauf bereit mit ${solverSteps.length} Schritten. Nutze Next Step oder starte die Animation.` + ); + setStatus("Solver-Ablauf bereit. Nutze Next Step oder Play Animation.", "#2563eb"); + drawBoard(); + return true; + } catch (error) { + if (requestId !== solverRequestId) { + return false; + } + + setSolverMessage("Der Solver-Ablauf konnte nicht geladen werden."); + setStatus("Der Solver-Ablauf konnte nicht geladen werden.", "red"); + return false; + } finally { + if (requestId === solverRequestId) { + solverLoading = false; + updateSolverControls(); + } + } +} + +async function nextSolverStep() { + if (solverLoading) { + return; + } + + if (!hasPreparedSolverTrace()) { + const prepared = await prepareSolutionTrace(); + + if (!prepared) { + return; + } + } + + if (isSolverPlaying()) { + stopSolverPlayback(false); + } + + if (!advanceSolverStep()) { + setStatus("Der Solver-Ablauf ist bereits abgeschlossen.", "#2563eb"); + } +} + +async function toggleSolverPlayback() { + if (solverLoading) { + return; + } + + if (isSolverPlaying()) { + stopSolverPlayback(); + setStatus("Solver-Animation pausiert.", "#2563eb"); + return; + } + + if (!hasPreparedSolverTrace()) { + const prepared = await prepareSolutionTrace(); + + if (!prepared) { + return; + } + } + + if (hasCompletedSolverTrace()) { + rewindSolverTrace(); + } + + setStatus("Solver-Animation laeuft.", "#2563eb"); + startSolverPlayback(true); +} + +async function solve() { + await prepareSolutionTrace(); +} + +async function instantSolve() { + if (solverLoading) { + return; + } + + const endpoint = mode === "rooks" ? "/solve_rooks" : "/solve"; + const requestId = ++solverRequestId; + + solverLoading = true; + stopSolverPlayback(false); + solverSteps = []; + solverStepIndex = -1; + currentSolverHighlight = null; + setSolverMessage(`Lade sofortige ${getPieceLabel(mode)}-Loesung fuer ${boardSize} x ${boardSize}...`); + setStatus(`Sofortige Loesung wird geladen.`, "#2563eb"); + updateSolverControls(); + + try { + const response = await fetch(`${endpoint}?size=${boardSize}`); + + if (!response.ok) { + throw new Error("instant solve request failed"); + } + + const solution = await response.json(); + + if (requestId !== solverRequestId) { + return; + } + + board = mode === "queens" && Array.isArray(solution[0]) ? solution[0].slice() : solution.slice(); + currentSolverHighlight = null; + setSolverMessage("Sofortige Loesung geladen. Starte Solve Step-by-Step, wenn du den Ablauf sehen moechtest."); + setStatus(`Loesung fuer ${boardSize} x ${boardSize} sofort geladen.`, "green"); + drawBoard(); + } catch (error) { + if (requestId !== solverRequestId) { + return; + } + + setSolverMessage("Die sofortige Loesung konnte nicht geladen werden."); + setStatus("Die sofortige Loesung konnte nicht geladen werden.", "red"); + } finally { + if (requestId === solverRequestId) { + solverLoading = false; + updateSolverControls(); + } + } +} + +function resetBoard(options = {}) { + const { + statusMessageText = "", + statusColor = "", + solverMessageText = `${getPieceLabel(mode)}-Solver bereit. ${DEFAULT_SOLVER_MESSAGE}` + } = options; + + discardSolverTrace(solverMessageText); board = createEmptyBoard(boardSize); - setStatus(); + currentSolverHighlight = null; + + if (statusMessageText) { + setStatus(statusMessageText, statusColor); + } else { + setStatus(); + } + drawBoard(); } @@ -401,7 +784,7 @@ function updateQueenList() { li.textContent = `${mode === "queens" ? "Dame" : "Turm"} ${count}: ${position}`; queenList.appendChild(li); - count++; + count += 1; } } @@ -422,7 +805,7 @@ async function saveGame() { }); if (!response.ok) { - setStatus("❌ Der Speicherstand konnte nicht gespeichert werden.", "red"); + setStatus("Der Speicherstand konnte nicht gespeichert werden.", "red"); return; } @@ -443,7 +826,7 @@ async function loadSavePoint(savePointId) { const response = await fetch(`/load/${savePointId}`); if (!response.ok) { - setStatus("❌ Dieser Speicherstand konnte nicht geladen werden.", "red"); + setStatus("Dieser Speicherstand konnte nicht geladen werden.", "red"); return; } @@ -460,7 +843,7 @@ async function toggleSavePointFavorite(savePointId) { }); if (!response.ok) { - setStatus("❌ Favorit konnte nicht aktualisiert werden.", "red"); + setStatus("Favorit konnte nicht aktualisiert werden.", "red"); return; } @@ -480,7 +863,7 @@ async function deleteSavePoint(savePointId) { }); if (!response.ok) { - setStatus("❌ Speicherpunkt konnte nicht geloescht werden.", "red"); + setStatus("Speicherpunkt konnte nicht geloescht werden.", "red"); return; } @@ -491,4 +874,7 @@ async function deleteSavePoint(savePointId) { pendingBoardSize = boardSize; updateModeButtons(); updateBoardSizeDisplays(boardSize); +updateSolverSpeed(solverSpeedMs); +setSolverMessage(`${getPieceLabel(mode)}-Solver bereit. ${DEFAULT_SOLVER_MESSAGE}`); +updateSolverControls(); drawBoard(); diff --git a/app/templates/dashboard.html b/app/templates/dashboard.html index 1c82d34..72fddbd 100644 --- a/app/templates/dashboard.html +++ b/app/templates/dashboard.html @@ -61,8 +61,31 @@

+
+
+
+

Solver-Ablauf

+

Bereite einen Ablauf vor und spiele ihn Schritt fuer Schritt ab.

+
+

Schritt 0 / 0

+
+ +
+ + + + +
+ +
+ + + 320 ms +
+
+
-