improved javascript structure, added load selection and made chessboard selectable
This commit is contained in:
Binary file not shown.
Binary file not shown.
+126
-53
@@ -1,20 +1,88 @@
|
|||||||
|
import json
|
||||||
|
import random
|
||||||
|
|
||||||
from flask import Blueprint, jsonify, render_template, request
|
from flask import Blueprint, jsonify, render_template, request
|
||||||
from flask_login import login_required, current_user
|
from flask_login import current_user, login_required
|
||||||
import random, json
|
|
||||||
from ..models.gamestate import GameState
|
|
||||||
from ..models.user import User
|
|
||||||
from .. import db
|
from .. import db
|
||||||
|
from ..models.gamestate import GameState
|
||||||
|
|
||||||
|
DEFAULT_BOARD_SIZE = 8
|
||||||
|
MIN_BOARD_SIZE = 4
|
||||||
|
MAX_BOARD_SIZE = 13
|
||||||
|
|
||||||
main_bp = Blueprint("main", __name__)
|
main_bp = Blueprint("main", __name__)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_board_size(raw_size, default=DEFAULT_BOARD_SIZE):
|
||||||
|
try:
|
||||||
|
board_size = int(raw_size)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
board_size = default
|
||||||
|
|
||||||
|
return max(MIN_BOARD_SIZE, min(MAX_BOARD_SIZE, board_size))
|
||||||
|
|
||||||
|
|
||||||
|
def 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("/")
|
@main_bp.route("/")
|
||||||
def home():
|
def home():
|
||||||
return render_template("index.html")
|
return render_template("index.html")
|
||||||
|
|
||||||
@main_bp.route('/dashboard')
|
|
||||||
|
@main_bp.route("/dashboard")
|
||||||
@login_required
|
@login_required
|
||||||
def dashboard():
|
def dashboard():
|
||||||
return render_template('dashboard.html', user=current_user)
|
return render_template("dashboard.html", user=current_user)
|
||||||
|
|
||||||
|
|
||||||
@main_bp.route("/check", methods=["POST"])
|
@main_bp.route("/check", methods=["POST"])
|
||||||
def check():
|
def check():
|
||||||
@@ -22,71 +90,48 @@ def check():
|
|||||||
board = data["board"]
|
board = data["board"]
|
||||||
row = data["row"]
|
row = data["row"]
|
||||||
col = data["col"]
|
col = data["col"]
|
||||||
|
piece_mode = data.get("mode", "queens")
|
||||||
|
|
||||||
for r in range(len(board)):
|
for current_row, current_col in enumerate(board):
|
||||||
c = board[r]
|
if current_col == -1 or current_row == row:
|
||||||
|
|
||||||
if c == -1 or r == row:
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# gleiche Spalte
|
same_column = current_col == col
|
||||||
if c == col:
|
same_diagonal = abs(current_col - col) == abs(current_row - row)
|
||||||
|
|
||||||
|
if same_column:
|
||||||
return jsonify({"valid": False})
|
return jsonify({"valid": False})
|
||||||
|
|
||||||
# gleiche Diagonale
|
if piece_mode == "queens" and same_diagonal:
|
||||||
if abs(c - col) == abs(r - row):
|
|
||||||
return jsonify({"valid": False})
|
return jsonify({"valid": False})
|
||||||
|
|
||||||
return jsonify({"valid": True})
|
return jsonify({"valid": True})
|
||||||
|
|
||||||
|
|
||||||
@main_bp.route("/solve")
|
@main_bp.route("/solve")
|
||||||
def solve():
|
def solve_queens_route():
|
||||||
n = 8
|
board_size = parse_board_size(request.args.get("size"))
|
||||||
solutions = []
|
return jsonify(solve_queens(board_size))
|
||||||
board = [-1] * n
|
|
||||||
|
|
||||||
def is_safe(row, col):
|
|
||||||
for r in range(row):
|
|
||||||
c = board[r]
|
|
||||||
if c == col or abs(c - col) == abs(r - row):
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def backtrack(row):
|
|
||||||
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")
|
@main_bp.route("/solve_rooks")
|
||||||
def solve_rooks_route():
|
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"])
|
@main_bp.route("/save", methods=["POST"])
|
||||||
|
@login_required
|
||||||
def save_game():
|
def save_game():
|
||||||
data = request.json
|
data = request.json
|
||||||
|
|
||||||
board = data["board"]
|
board = data["board"]
|
||||||
mode = data["mode"]
|
piece_mode = data["mode"]
|
||||||
|
|
||||||
game = GameState(
|
game = GameState(
|
||||||
user_id=current_user.id,
|
user_id=current_user.id,
|
||||||
board=json.dumps(board),
|
board=json.dumps(board),
|
||||||
mode=mode
|
mode=piece_mode
|
||||||
)
|
)
|
||||||
|
|
||||||
db.session.add(game)
|
db.session.add(game)
|
||||||
@@ -94,15 +139,43 @@ def save_game():
|
|||||||
|
|
||||||
return jsonify({"status": "saved"})
|
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:
|
@main_bp.route("/save_points")
|
||||||
return jsonify({"board": [-1]*8, "mode": "queens"})
|
@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({
|
return jsonify({
|
||||||
"board": json.loads(game.board),
|
"board": json.loads(latest_game.board),
|
||||||
"mode": game.mode
|
"mode": latest_game.mode
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@main_bp.route("/load/<int:game_id>")
|
||||||
|
@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))
|
||||||
|
|||||||
@@ -44,16 +44,24 @@ body {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.board {
|
||||||
|
overflow-x: auto;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
/* ♟️ Board */
|
/* ♟️ Board */
|
||||||
.board-grid {
|
.board-grid {
|
||||||
|
--label-size: 30px;
|
||||||
|
--cell-size: 60px;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 30px repeat(8, 60px);
|
grid-template-columns: var(--label-size) repeat(var(--board-size, 8), var(--cell-size));
|
||||||
grid-template-rows: 30px repeat(8, 60px);
|
grid-template-rows: var(--label-size) repeat(var(--board-size, 8), var(--cell-size));
|
||||||
margin: 20px auto;
|
margin: 20px auto;
|
||||||
width: max-content;
|
width: max-content;
|
||||||
border: 6px solid #1e293b;
|
border: 6px solid #1e293b;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
@@ -63,17 +71,18 @@ body {
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
background: #aec8eb;
|
background: #aec8eb;
|
||||||
color: #1e293b;
|
color: #1e293b;
|
||||||
|
width: var(--label-size);
|
||||||
|
height: var(--label-size);
|
||||||
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.cell {
|
.cell {
|
||||||
width: 60px;
|
width: var(--cell-size);
|
||||||
height: 60px;
|
height: var(--cell-size);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
font-size: 30px;
|
font-size: calc(var(--cell-size) * 0.5);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: 0.2s;
|
transition: 0.2s;
|
||||||
}
|
}
|
||||||
@@ -153,3 +162,118 @@ body {
|
|||||||
.btn.active {
|
.btn.active {
|
||||||
background: #22c55e;
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+257
-132
@@ -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) {
|
function setMode(newMode) {
|
||||||
mode = newMode;
|
mode = newMode;
|
||||||
resetBoard();
|
resetBoard();
|
||||||
|
updateModeButtons();
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
let board = Array(8).fill(-1);
|
|
||||||
const boardDiv = document.getElementById("board");
|
|
||||||
|
|
||||||
function getConflicts() {
|
function getConflicts() {
|
||||||
let conflicts = [];
|
const conflicts = [];
|
||||||
|
|
||||||
for (let r1 = 0; r1 < 8; r1++) {
|
for (let rowA = 0; rowA < boardSize; rowA++) {
|
||||||
if (board[r1] === -1) continue;
|
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]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🔴 Damen: Spalte + Diagonale
|
for (let rowB = rowA + 1; rowB < boardSize; rowB++) {
|
||||||
else if (mode === "queens") {
|
if (board[rowB] === EMPTY_CELL) {
|
||||||
if (
|
continue;
|
||||||
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() {
|
function checkSolution() {
|
||||||
const status = document.getElementById("statusMessage");
|
const allPlaced = board.every((col) => col !== EMPTY_CELL);
|
||||||
|
|
||||||
const allPlaced = board.every(col => col !== -1);
|
|
||||||
|
|
||||||
if (!allPlaced) {
|
if (!allPlaced) {
|
||||||
status.textContent = "❗ Noch nicht alle Figuren platziert.";
|
setStatus("❗ Noch nicht alle Figuren platziert.", "orange");
|
||||||
status.style.color = "orange";
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const conflicts = getConflicts();
|
if (getConflicts().length > 0) {
|
||||||
|
setStatus("❌ Es gibt Konflikte!", "red");
|
||||||
if (conflicts.length > 0) {
|
|
||||||
status.textContent = "❌ Es gibt Konflikte!";
|
|
||||||
status.style.color = "red";
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
status.textContent = "🎉 Perfekt gelöst!";
|
setStatus("🎉 Perfekt geloest!", "green");
|
||||||
status.style.color = "green";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawBoard() {
|
function drawBoard() {
|
||||||
boardDiv.innerHTML = "";
|
boardDiv.innerHTML = "";
|
||||||
boardDiv.className = "board-grid";
|
boardDiv.className = "board-grid";
|
||||||
|
updateBoardLayout();
|
||||||
|
|
||||||
const conflicts = getConflicts();
|
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");
|
const cell = document.createElement("div");
|
||||||
|
|
||||||
// 🔹 Ecke oben links
|
|
||||||
if (row === -1 && col === -1) {
|
if (row === -1 && col === -1) {
|
||||||
cell.classList.add("label");
|
cell.classList.add("label");
|
||||||
}
|
} else if (row === -1) {
|
||||||
|
cell.textContent = getColumnLabel(col);
|
||||||
// 🔹 Buchstaben (oben)
|
|
||||||
else if (row === -1) {
|
|
||||||
cell.textContent = letters[col];
|
|
||||||
cell.classList.add("label");
|
cell.classList.add("label");
|
||||||
}
|
} else if (col === -1) {
|
||||||
|
cell.textContent = boardSize - row;
|
||||||
// 🔹 Zahlen (links)
|
|
||||||
else if (col === -1) {
|
|
||||||
cell.textContent = 8 - row;
|
|
||||||
cell.classList.add("label");
|
cell.classList.add("label");
|
||||||
}
|
|
||||||
|
|
||||||
// 🔹 echtes Spielfeld
|
|
||||||
else {
|
|
||||||
cell.classList.add("cell");
|
|
||||||
|
|
||||||
if ((row + col) % 2 === 0) {
|
|
||||||
cell.classList.add("white");
|
|
||||||
} else {
|
} else {
|
||||||
cell.classList.add("black");
|
cell.classList.add("cell", (row + col) % 2 === 0 ? "white" : "black");
|
||||||
}
|
|
||||||
|
|
||||||
if (board[row] === col) {
|
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");
|
cell.classList.add("invalid");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -136,65 +155,163 @@ function drawBoard() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function placeQueen(row, col) {
|
function placeQueen(row, col) {
|
||||||
if (board[row] === col) {
|
board[row] = board[row] === col ? EMPTY_CELL : col;
|
||||||
board[row] = -1;
|
|
||||||
} else {
|
|
||||||
board[row] = col;
|
|
||||||
}
|
|
||||||
drawBoard();
|
drawBoard();
|
||||||
updateQueenList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function solve() {
|
function previewBoardSize(size) {
|
||||||
|
pendingBoardSize = clampBoardSize(size);
|
||||||
|
sidebarBoardSizeValue.textContent = pendingBoardSize;
|
||||||
|
sidebarBoardSizeValueMirror.textContent = pendingBoardSize;
|
||||||
|
}
|
||||||
|
|
||||||
if (mode === "rooks") {
|
function applyBoardSize() {
|
||||||
const res = await fetch("/solve_rooks");
|
boardSize = pendingBoardSize;
|
||||||
const solution = await res.json();
|
board = createEmptyBoard(boardSize);
|
||||||
|
updateBoardSizeDisplays(boardSize);
|
||||||
board = solution;
|
setStatus(`Brettgroesse auf ${boardSize} x ${boardSize} gesetzt.`, "#2563eb");
|
||||||
drawBoard();
|
drawBoard();
|
||||||
|
toggleSidebar(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = '<p class="sidebar-hint">Noch keine Speicherstaende gefunden.</p>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Damen (wie vorher)
|
for (const savePoint of savePoints) {
|
||||||
const res = await fetch("/solve");
|
const item = document.createElement("article");
|
||||||
const solution = await res.json();
|
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();
|
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() {
|
function resetBoard() {
|
||||||
const status = document.getElementById("statusMessage");
|
board = createEmptyBoard(boardSize);
|
||||||
board = Array(8).fill(-1);
|
setStatus();
|
||||||
drawBoard();
|
drawBoard();
|
||||||
updateQueenList();
|
|
||||||
status.textContent = "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
drawBoard();
|
|
||||||
|
|
||||||
function updateQueenList() {
|
function updateQueenList() {
|
||||||
const list = document.getElementById("queenList");
|
queenList.innerHTML = "";
|
||||||
list.innerHTML = "";
|
|
||||||
|
|
||||||
const letters = ["A", "B", "C", "D", "E", "F", "G", "H"];
|
|
||||||
|
|
||||||
let count = 1;
|
let count = 1;
|
||||||
|
|
||||||
for (let row = 0; row < 8; row++) {
|
for (let row = 0; row < boardSize; row++) {
|
||||||
if (board[row] !== -1) {
|
if (board[row] === EMPTY_CELL) {
|
||||||
let col = board[row];
|
continue;
|
||||||
|
|
||||||
let pos = letters[col] + (8 - row);
|
|
||||||
|
|
||||||
const li = document.createElement("li");
|
|
||||||
li.textContent = `${mode === "queens" ? "Dame" : "Turm"} ${count}: ${pos}`;
|
|
||||||
|
|
||||||
list.appendChild(li);
|
|
||||||
count++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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"
|
"Content-Type": "application/json"
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
board: board,
|
board,
|
||||||
mode: mode
|
mode,
|
||||||
|
boardSize
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await refreshSavePoints();
|
||||||
alert("💾 Spiel gespeichert!");
|
alert("💾 Spiel gespeichert!");
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadGame() {
|
async function loadGame() {
|
||||||
const res = await fetch("/load");
|
const response = await fetch("/load");
|
||||||
const data = await res.json();
|
const data = await response.json();
|
||||||
|
applyLoadedGame(data);
|
||||||
board = data.board;
|
|
||||||
mode = data.mode;
|
|
||||||
|
|
||||||
// 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");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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(`Speicherstand #${savePointId} geladen.`, "green");
|
||||||
|
toggleSavePointsPanel(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
pendingBoardSize = boardSize;
|
||||||
|
updateModeButtons();
|
||||||
|
updateBoardSizeDisplays(boardSize);
|
||||||
drawBoard();
|
drawBoard();
|
||||||
}
|
|
||||||
|
|||||||
@@ -48,12 +48,15 @@
|
|||||||
<div class="mode-switch">
|
<div class="mode-switch">
|
||||||
<button class="btn active" onclick="setMode('queens')" id="btnQueens">♛ Damen</button>
|
<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="setMode('rooks')" id="btnRooks">♜ Türme</button>
|
||||||
|
<button class="btn secondary" onclick="toggleSidebar()" id="btnSidebar">Board Settings</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<!-- ♟️ Game Card -->
|
<!-- ♟️ Game Card -->
|
||||||
<div class="card game-card">
|
<div class="card game-card">
|
||||||
<h2>♟️ 8-Damen Spiel</h2>
|
<h2>♟️ Damen- und Tuerme-Spiel</h2>
|
||||||
|
<p class="board-size-info">Aktuelle Brettgroesse: <span id="boardSizeValue">8</span> x <span
|
||||||
|
id="boardSizeValueMirror">8</span></p>
|
||||||
|
|
||||||
<p id="statusMessage"></p>
|
<p id="statusMessage"></p>
|
||||||
<div class="board" id="board"></div>
|
<div class="board" id="board"></div>
|
||||||
@@ -64,16 +67,49 @@
|
|||||||
<button class="btn secondary" onclick="resetBoard()">Reset</button>
|
<button class="btn secondary" onclick="resetBoard()">Reset</button>
|
||||||
|
|
||||||
<button class="btn" onclick="saveGame()">💾 Save</button>
|
<button class="btn" onclick="saveGame()">💾 Save</button>
|
||||||
<button class="btn" onclick="loadGame()">📂 Load</button>
|
<button class="btn" onclick="openSavePointsPanel()">📂 Load</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h3>📋 Damen Positionen</h3>
|
<h3>📋 Figuren Positionen</h3>
|
||||||
<ul id="queenList"></ul>
|
<ul id="queenList"></ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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-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-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="savePointsBackdrop" onclick="toggleSavePointsPanel(false)"></div>
|
||||||
|
|
||||||
<script src="../static/js/dashboard.js"></script>
|
<script src="../static/js/dashboard.js"></script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
Reference in New Issue
Block a user