Issue-20 echnaged some things mentioned in the issues.
This commit is contained in:
+63
-17
@@ -23,6 +23,52 @@ MAX_BOARD_SIZE = 13
|
||||
main_bp = Blueprint("main", __name__)
|
||||
|
||||
|
||||
def create_empty_board(board_size):
|
||||
"""Create a square board matrix with no placed pieces."""
|
||||
return [[0 for _ in range(board_size)] for _ in range(board_size)]
|
||||
|
||||
|
||||
def build_board_from_positions(piece_positions, board_size):
|
||||
"""Convert the legacy one-piece-per-row format into a board matrix."""
|
||||
board = create_empty_board(board_size)
|
||||
|
||||
for row, col in enumerate(piece_positions):
|
||||
if isinstance(col, int) and 0 <= col < board_size:
|
||||
board[row][col] = 1
|
||||
|
||||
return board
|
||||
|
||||
|
||||
def normalize_board(candidate_board, board_size):
|
||||
"""Normalize stored boards so legacy and matrix formats can both be used."""
|
||||
if not isinstance(candidate_board, list) or len(candidate_board) != board_size:
|
||||
return create_empty_board(board_size)
|
||||
|
||||
if all(isinstance(row, list) and len(row) == board_size for row in candidate_board):
|
||||
normalized_board = create_empty_board(board_size)
|
||||
|
||||
for row_index, row in enumerate(candidate_board):
|
||||
for col_index, value in enumerate(row):
|
||||
normalized_board[row_index][col_index] = 1 if value else 0
|
||||
|
||||
return normalized_board
|
||||
|
||||
return build_board_from_positions(candidate_board, board_size)
|
||||
|
||||
|
||||
def iter_piece_positions(board):
|
||||
"""Yield every occupied square from the normalized board matrix."""
|
||||
for row_index, row in enumerate(board):
|
||||
for col_index, value in enumerate(row):
|
||||
if value:
|
||||
yield row_index, col_index
|
||||
|
||||
|
||||
def count_placed_pieces(board):
|
||||
"""Count how many pieces are currently placed on the board."""
|
||||
return sum(1 for _ in iter_piece_positions(board))
|
||||
|
||||
|
||||
def parse_board_size(raw_size, default=DEFAULT_BOARD_SIZE):
|
||||
"""Convert a user-supplied board size into a safe integer range."""
|
||||
try:
|
||||
@@ -119,7 +165,7 @@ def build_queens_trace(board_size):
|
||||
"type": "solution",
|
||||
"row": None,
|
||||
"col": None,
|
||||
"board": queen_positions[:],
|
||||
"board": build_board_from_positions(queen_positions, board_size),
|
||||
"message": f"Lösung gefunden. Alle {board_size} Damen stehen ohne Konflikte."
|
||||
})
|
||||
return True
|
||||
@@ -136,7 +182,7 @@ def build_queens_trace(board_size):
|
||||
"type": "place",
|
||||
"row": row,
|
||||
"col": col,
|
||||
"board": queen_positions[:],
|
||||
"board": build_board_from_positions(queen_positions, board_size),
|
||||
"message": f"Dame auf {position} gesetzt."
|
||||
})
|
||||
|
||||
@@ -148,7 +194,7 @@ def build_queens_trace(board_size):
|
||||
"type": "remove",
|
||||
"row": row,
|
||||
"col": col,
|
||||
"board": queen_positions[:],
|
||||
"board": build_board_from_positions(queen_positions, board_size),
|
||||
"message": f"Backtracking: Dame von {position} entfernt."
|
||||
})
|
||||
|
||||
@@ -159,7 +205,7 @@ def build_queens_trace(board_size):
|
||||
return {
|
||||
"mode": "queens",
|
||||
"board_size": board_size,
|
||||
"initial_board": [-1] * board_size,
|
||||
"initial_board": create_empty_board(board_size),
|
||||
"steps": steps,
|
||||
"solved": solved
|
||||
}
|
||||
@@ -168,11 +214,11 @@ def build_queens_trace(board_size):
|
||||
def build_rooks_trace(board_size):
|
||||
"""Build a simple placement trace for the rook mode."""
|
||||
rook_positions = solve_rooks(board_size)
|
||||
board = [-1] * board_size
|
||||
board = create_empty_board(board_size)
|
||||
steps = []
|
||||
|
||||
for row, col in enumerate(rook_positions):
|
||||
board[row] = col
|
||||
board[row][col] = 1
|
||||
position = get_position_label(row, col, board_size)
|
||||
# As with the queens trace, each step includes a full board snapshot for
|
||||
# straightforward frontend rendering.
|
||||
@@ -180,7 +226,7 @@ def build_rooks_trace(board_size):
|
||||
"type": "place",
|
||||
"row": row,
|
||||
"col": col,
|
||||
"board": board[:],
|
||||
"board": [board_row[:] for board_row in board],
|
||||
"message": f"Turm auf {position} gesetzt."
|
||||
})
|
||||
|
||||
@@ -188,14 +234,14 @@ def build_rooks_trace(board_size):
|
||||
"type": "solution",
|
||||
"row": None,
|
||||
"col": None,
|
||||
"board": board[:],
|
||||
"board": [board_row[:] for board_row in board],
|
||||
"message": f"Loesung gefunden. Alle {board_size} Tuerme stehen ohne Konflikte."
|
||||
})
|
||||
|
||||
return {
|
||||
"mode": "rooks",
|
||||
"board_size": board_size,
|
||||
"initial_board": [-1] * board_size,
|
||||
"initial_board": create_empty_board(board_size),
|
||||
"steps": steps,
|
||||
"solved": True
|
||||
}
|
||||
@@ -203,7 +249,7 @@ def build_rooks_trace(board_size):
|
||||
|
||||
def serialize_game_state(game_state):
|
||||
"""Convert a database model into JSON-friendly data for the frontend."""
|
||||
board = json.loads(game_state.board)
|
||||
board = normalize_board(json.loads(game_state.board), game_state.board_size)
|
||||
|
||||
return {
|
||||
"id": game_state.id,
|
||||
@@ -213,7 +259,7 @@ def serialize_game_state(game_state):
|
||||
"save_name": game_state.save_name,
|
||||
"save_note": game_state.save_note,
|
||||
"is_favorite": game_state.is_favorite,
|
||||
"pieces_placed": sum(1 for column in board if column != -1),
|
||||
"pieces_placed": count_placed_pieces(board),
|
||||
"step_count": game_state.step_count,
|
||||
"elapsed_seconds": game_state.elapsed_seconds,
|
||||
"is_solved": game_state.is_solved,
|
||||
@@ -285,17 +331,17 @@ def check():
|
||||
row = data["row"]
|
||||
col = data["col"]
|
||||
piece_mode = data.get("mode", "queens")
|
||||
normalized_board = normalize_board(board, len(board))
|
||||
|
||||
# The board is stored as a list where the index is the row and the value is
|
||||
# the column of the placed piece. -1 means the row is still empty.
|
||||
for current_row, current_col in enumerate(board):
|
||||
if current_col == -1 or current_row == row:
|
||||
for current_row, current_col in iter_piece_positions(normalized_board):
|
||||
if current_row == row and current_col == col:
|
||||
continue
|
||||
|
||||
same_row = current_row == row
|
||||
same_column = current_col == col
|
||||
same_diagonal = abs(current_col - col) == abs(current_row - row)
|
||||
|
||||
if same_column:
|
||||
if same_row or same_column:
|
||||
return jsonify({"valid": False})
|
||||
|
||||
# Diagonal conflicts only matter for queens, not for rooks.
|
||||
@@ -425,7 +471,7 @@ def load_game():
|
||||
|
||||
# If the user has never saved a game, return a clean default board.
|
||||
if not latest_game:
|
||||
return jsonify({"board": [-1] * DEFAULT_BOARD_SIZE, "mode": "queens"})
|
||||
return jsonify({"board": create_empty_board(DEFAULT_BOARD_SIZE), "mode": "queens"})
|
||||
|
||||
return jsonify(serialize_game_state(latest_game))
|
||||
|
||||
|
||||
@@ -483,9 +483,17 @@ textarea {
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.board {
|
||||
.board-stage {
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
justify-items: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.board-shell {
|
||||
overflow-x: auto;
|
||||
padding-bottom: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.board-grid {
|
||||
@@ -544,30 +552,30 @@ textarea {
|
||||
background: #d65555 !important;
|
||||
}
|
||||
|
||||
.solver-panel {
|
||||
margin-top: 18px;
|
||||
padding: 18px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(31, 49, 64, 0.1);
|
||||
background: rgba(255, 250, 242, 0.74);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.solver-panel-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.solver-panel h3 {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.solver-message {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.board-navigator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
width: min(100%, 620px);
|
||||
}
|
||||
|
||||
.board-nav-button {
|
||||
min-width: 150px;
|
||||
}
|
||||
|
||||
.solver-message-inline {
|
||||
margin: 0 auto;
|
||||
max-width: 760px;
|
||||
text-align: center;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.solver-step-info {
|
||||
margin: 0;
|
||||
padding: 8px 12px;
|
||||
@@ -579,63 +587,31 @@ textarea {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.solver-groups {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.35fr) minmax(260px, 0.65fr);
|
||||
gap: 16px;
|
||||
margin-top: 18px;
|
||||
}
|
||||
|
||||
.solver-group {
|
||||
padding: 18px;
|
||||
border: 1px solid rgba(31, 49, 64, 0.1);
|
||||
background: rgba(255, 255, 255, 0.58);
|
||||
}
|
||||
|
||||
.solver-group h4 {
|
||||
margin: 0 0 8px;
|
||||
color: #182029;
|
||||
font-size: 1.15rem;
|
||||
}
|
||||
|
||||
.solver-group-text {
|
||||
margin: 0;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.solver-controls {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.instant-solver-actions {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.instant-solver-actions .btn {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.solver-speed {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr auto;
|
||||
align-items: center;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
grid-template-areas:
|
||||
"label label"
|
||||
"input value";
|
||||
align-items: end;
|
||||
gap: 12px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.solver-speed input[type="range"] {
|
||||
grid-area: input;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.solver-speed-label {
|
||||
grid-area: label;
|
||||
font-weight: 700;
|
||||
color: var(--navy);
|
||||
}
|
||||
|
||||
.solver-speed-value {
|
||||
grid-area: value;
|
||||
min-width: 62px;
|
||||
text-align: right;
|
||||
color: var(--muted);
|
||||
@@ -650,19 +626,6 @@ textarea {
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.controls-save-row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
width: min(420px, 100%);
|
||||
}
|
||||
|
||||
.settings-sidebar-actions {
|
||||
margin-top: 18px;
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.section-header,
|
||||
.history-header,
|
||||
.sidebar-header,
|
||||
@@ -786,6 +749,34 @@ textarea {
|
||||
width: min(400px, 94vw);
|
||||
}
|
||||
|
||||
.settings-section {
|
||||
margin-top: 22px;
|
||||
padding-top: 22px;
|
||||
border-top: 1px solid rgba(31, 49, 64, 0.1);
|
||||
}
|
||||
|
||||
.settings-section:first-of-type {
|
||||
margin-top: 0;
|
||||
padding-top: 0;
|
||||
border-top: 0;
|
||||
}
|
||||
|
||||
.settings-section h4 {
|
||||
margin: 0;
|
||||
color: #182029;
|
||||
font-size: 1.15rem;
|
||||
}
|
||||
|
||||
.settings-section .section-kicker {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.settings-action-grid {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.sidebar-label {
|
||||
display: block;
|
||||
margin-top: 18px;
|
||||
@@ -980,14 +971,6 @@ textarea {
|
||||
}
|
||||
|
||||
@media (max-width: 980px) {
|
||||
.solver-groups {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.solver-controls {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.game-card-header,
|
||||
.history-header,
|
||||
.section-header {
|
||||
@@ -1040,7 +1023,7 @@ textarea {
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
.board {
|
||||
.board-shell {
|
||||
margin-left: -4px;
|
||||
margin-right: -4px;
|
||||
padding-bottom: 4px;
|
||||
@@ -1061,6 +1044,10 @@ textarea {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.board-navigator {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.mode-switch .btn {
|
||||
min-width: min(100%, 280px);
|
||||
}
|
||||
@@ -1070,14 +1057,11 @@ textarea {
|
||||
}
|
||||
|
||||
.controls>.btn,
|
||||
.board-nav-button,
|
||||
.save-point-actions .btn {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.controls-save-row {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: min(100%, calc(100vw - 24px));
|
||||
}
|
||||
|
||||
+230
-110
@@ -2,7 +2,8 @@
|
||||
const DEFAULT_BOARD_SIZE = 8;
|
||||
const MIN_BOARD_SIZE = 4;
|
||||
const MAX_BOARD_SIZE = 13;
|
||||
const EMPTY_CELL = -1;
|
||||
const EMPTY_CELL = 0;
|
||||
const OCCUPIED_CELL = 1;
|
||||
const GAME_STORAGE_KEY = "eight-queens-dashboard-session-v1";
|
||||
|
||||
const AppLanguage = window.AppLanguage;
|
||||
@@ -25,6 +26,7 @@ let solverSpeedMs = 320;
|
||||
let solverLoading = false;
|
||||
let solverRequestId = 0;
|
||||
let currentSolverHighlight = null;
|
||||
let solverInitialBoard = createEmptyBoard(boardSize);
|
||||
|
||||
// Cached DOM references
|
||||
const boardDiv = document.getElementById("board");
|
||||
@@ -51,8 +53,10 @@ const btnQueens = document.getElementById("btnQueens");
|
||||
const btnRooks = document.getElementById("btnRooks");
|
||||
const btnPrepareSolve = document.getElementById("btnPrepareSolve");
|
||||
const btnInstantSolve = document.getElementById("btnInstantSolve");
|
||||
const btnPrevStep = document.getElementById("btnPrevStep");
|
||||
const btnNextStep = document.getElementById("btnNextStep");
|
||||
const btnPlayPause = document.getElementById("btnPlayPause");
|
||||
const boardNavigator = document.getElementById("boardNavigator");
|
||||
const solverMessage = document.getElementById("solverMessage");
|
||||
const solverStepInfo = document.getElementById("solverStepInfo");
|
||||
const solverSpeedInput = document.getElementById("solverSpeedInput");
|
||||
@@ -95,22 +99,22 @@ const MODE_COPY = {
|
||||
plural: "Damen",
|
||||
eyebrow: "Damen-Modus",
|
||||
gameTitle: "Damen-Spielbrett",
|
||||
boardText: "Platziere Damen so, dass keine zwei dieselbe Spalte oder Diagonale teilen.",
|
||||
infoTitle: "Spielregeln und Acht-Damen-Problem",
|
||||
infoRuleText: "Im Damen-Modus dürfen sich Damen weder in derselben Spalte noch diagonal angreifen. Mit einem Klick setzt oder entfernst du eine Figur.",
|
||||
infoProblemHeading: "Was ist das Acht-Damen-Problem?",
|
||||
infoProblemText: "Das klassische Acht-Damen-Problem fragt nach einer Anordnung von acht Damen auf einem 8x8-Brett, bei der sich keine zwei Damen schlagen. Es ist ein bekanntes Beispiel für kombinatorische Suche und Backtracking."
|
||||
boardText: "Platziere Damen frei auf dem Brett. Konflikte entstehen in derselben Reihe, Spalte oder Diagonale.",
|
||||
infoTitle: "Spielanleitung",
|
||||
infoRuleText: "In diesem Modus spielst du mit Damen. Eine Dame ist die Figur mit dem Symbol ♛. Sie darf keine andere Dame in derselben waagerechten Reihe, senkrechten Spalte oder schrägen Diagonale treffen. Mit einem Klick setzt du eine Dame auf ein freies Feld oder entfernst sie wieder.",
|
||||
infoProblemHeading: "Was ist eine Dame?",
|
||||
infoProblemText: "Eine Dame ist eine Schachfigur. In diesem Spiel musst du keine Schachregeln lernen, sondern nur wissen: Eine Dame kann gerade und schräg über das Brett wirken. Darum müssen Damen genug Abstand haben, damit sie sich nicht gegenseitig erreichen."
|
||||
},
|
||||
rooks: {
|
||||
singular: "Turm",
|
||||
plural: "Türme",
|
||||
eyebrow: "Türme-Modus",
|
||||
gameTitle: "Türme-Spielbrett",
|
||||
boardText: "Platziere Türme so, dass keine zwei dieselbe Spalte teilen.",
|
||||
infoTitle: "Spielregeln und Acht-Türme-Problem",
|
||||
infoRuleText: "Im Türme-Modus dürfen sich Türme nicht in derselben Spalte stehen. Diagonalen spielen hier keine Rolle. Mit einem Klick setzt oder entfernst du eine Figur.",
|
||||
infoProblemHeading: "Was ist das Acht-Türme-Problem?",
|
||||
infoProblemText: "Beim Acht-Türme-Problem suchst du eine Anordnung von acht Türmen auf einem 8x8-Brett, bei der sich keine zwei Türme angreifen. Es ist eine einfachere Variante, um systematische Suche und Konfliktprüfung sichtbar zu machen."
|
||||
boardText: "Platziere Türme frei auf dem Brett. Konflikte entstehen in derselben Reihe oder Spalte.",
|
||||
infoTitle: "Spielanleitung",
|
||||
infoRuleText: "In diesem Modus spielst du mit Türmen. Ein Turm ist die Figur mit dem Symbol ♜. Er darf keinen anderen Turm in derselben waagerechten Reihe oder senkrechten Spalte treffen. Schräge Diagonalen sind hier egal. Mit einem Klick setzt du einen Turm auf ein freies Feld oder entfernst ihn wieder.",
|
||||
infoProblemHeading: "Was ist ein Turm?",
|
||||
infoProblemText: "Ein Turm ist eine Schachfigur. Für dieses Spiel reicht eine einfache Regel: Ein Turm wirkt nur gerade nach oben, unten, links und rechts. Darum dürfen zwei Türme nicht in derselben Reihe oder Spalte stehen."
|
||||
}
|
||||
},
|
||||
en: {
|
||||
@@ -119,22 +123,22 @@ const MODE_COPY = {
|
||||
plural: "Queens",
|
||||
eyebrow: "Queens Mode",
|
||||
gameTitle: "Queens Board",
|
||||
boardText: "Place queens so that no two share the same column or diagonal.",
|
||||
infoTitle: "Rules and the Eight Queens Puzzle",
|
||||
infoRuleText: "In queens mode, queens may not attack each other in the same column or on diagonals. Click once to place or remove a piece.",
|
||||
infoProblemHeading: "What is the Eight Queens puzzle?",
|
||||
infoProblemText: "The classic Eight Queens puzzle asks for eight queens on an 8x8 board so that no two queens attack each other. It is a well-known example of combinatorial search and backtracking."
|
||||
boardText: "Place queens freely on the board. Conflicts happen in the same row, column, or diagonal.",
|
||||
infoTitle: "How To Play",
|
||||
infoRuleText: "In this mode you play with queens. A queen is the piece shown with the symbol ♛. It must not reach another queen in the same horizontal row, vertical column, or diagonal line. Click once to place a queen on an empty square or remove it again.",
|
||||
infoProblemHeading: "What is a queen?",
|
||||
infoProblemText: "A queen is a chess piece. You do not need full chess knowledge here. You only need to know that a queen can affect the board in straight and diagonal lines. That is why queens need enough distance so they cannot reach each other."
|
||||
},
|
||||
rooks: {
|
||||
singular: "Rook",
|
||||
plural: "Rooks",
|
||||
eyebrow: "Rooks Mode",
|
||||
gameTitle: "Rooks Board",
|
||||
boardText: "Place rooks so that no two share the same column.",
|
||||
infoTitle: "Rules and the Eight Rooks Puzzle",
|
||||
infoRuleText: "In rooks mode, rooks may not stand in the same column. Diagonals do not matter here. Click once to place or remove a piece.",
|
||||
infoProblemHeading: "What is the Eight Rooks puzzle?",
|
||||
infoProblemText: "In the Eight Rooks puzzle, you arrange eight rooks on an 8x8 board so that no two rooks attack each other. It is a simpler variant that helps visualize systematic search and conflict checking."
|
||||
boardText: "Place rooks freely on the board. Conflicts happen in the same row or column.",
|
||||
infoTitle: "How To Play",
|
||||
infoRuleText: "In this mode you play with rooks. A rook is the piece shown with the symbol ♜. It must not reach another rook in the same horizontal row or vertical column. Diagonal lines do not matter here. Click once to place a rook on an empty square or remove it again.",
|
||||
infoProblemHeading: "What is a rook?",
|
||||
infoProblemText: "A rook is a chess piece. For this game you only need one simple idea: a rook affects the board only in straight lines up, down, left, and right. That is why two rooks must not stand in the same row or column."
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -149,11 +153,11 @@ const translations = {
|
||||
accountShortcutAria: "Konto und Login-Einstellungen öffnen",
|
||||
introKicker: "Puzzle-Modus",
|
||||
introTitle: "Wähl deinen Modus und löse das Brett auf deine Art.",
|
||||
introText: "Wechsle zwischen Damen und Türmen, nutze Animation oder Sofort-Lösung und öffne die Brett-Einstellungen jederzeit ueber den linken Seiten-Tab.",
|
||||
introText: "Wechsle zwischen Damen und Türmen, nutze Animation oder Sofort-Lösung und öffne die Spiel-Einstellungen jederzeit ueber den linken Seiten-Tab.",
|
||||
modeSwitchAria: "Spielmodus",
|
||||
btnQueens: "Damen",
|
||||
btnRooks: "Türme",
|
||||
infoButton: "Regeln & Hilfe",
|
||||
infoButton: "Spielanleitung",
|
||||
gameStatsAria: "Spielstatus",
|
||||
stepsLabel: "Schritte",
|
||||
timeLabel: "Zeit",
|
||||
@@ -167,26 +171,35 @@ const translations = {
|
||||
animationSolverText: "Bereite den Lösungsweg vor, gehe ihn manuell Schritt für Schritt durch oder spiele die Animation automatisch ab.",
|
||||
instantSolverTitle: "Sofort-Lösung",
|
||||
instantSolverText: "Lade direkt eine gültige Lösung, ohne Zwischenschritte oder Animation.",
|
||||
defaultSolverMessage: "Bereite einen Ablauf vor und spiele ihn Schritt für Schritt ab.",
|
||||
defaultSolverMessage: "Lade eine Animation und steuere sie am Brett oder spiele sie automatisch ab.",
|
||||
solverReady: ({ modeLabel }) => `Solver für ${modeLabel} bereit. ${t("defaultSolverMessage")}`,
|
||||
solverSpeedLabel: "Animationsgeschwindigkeit",
|
||||
btnPrepareSolve: "Animation vorbereiten",
|
||||
btnPrepareSolveLoading: "Animation wird vorbereitet...",
|
||||
btnNextStep: "Nächster Schritt",
|
||||
btnPrepareSolve: "Animation laden",
|
||||
btnPrepareSolveLoading: "Animation wird geladen...",
|
||||
btnPrevStep: "Zurück",
|
||||
btnNextStep: "Vorwärts",
|
||||
btnPlayPause: "Animation abspielen",
|
||||
btnPlayReplay: "Animation erneut abspielen",
|
||||
btnPlayPauseRunning: "Animation pausieren",
|
||||
btnInstantSolve: "Sofort lösen",
|
||||
btnReset: "Brett zurücksetzen",
|
||||
settingsTrigger: "Brett-Einstellungen",
|
||||
settingsSidebarTitle: "Brett-Einstellungen",
|
||||
settingsTrigger: "Spiel-Einstellungen",
|
||||
settingsSidebarTitle: "Spiel-Einstellungen",
|
||||
settingsSidebarClose: "Schliessen",
|
||||
boardSettingsKicker: "Brett",
|
||||
boardSettingsTitle: "Brettgrösse",
|
||||
settingsSidebarText: "Passe die Brettgroesse zwischen 4 und 13 an.",
|
||||
boardSizeInputLabel: "Grösse",
|
||||
applyBoardSizeButton: "Grösse anwenden",
|
||||
settingsSidebarHint: "Beim ändern wird das Brett zur Sicherheit zurückgesetzt. Auf dem Handy ist 8x8 eingeplant, grössere Bretter können abgeschnitten wirken.",
|
||||
saveSettingsKicker: "Speichern",
|
||||
saveSettingsTitle: "Fortschritt speichern",
|
||||
saveSettingsText: "Öffne hier die Optionen zum Speichern oder Laden deiner Save-Points.",
|
||||
solutionSettingsKicker: "Lösung",
|
||||
solutionSettingsTitle: "Lösungswerkzeuge",
|
||||
solutionSettingsText: "Lade eine Animation, steuere sie am Brett mit Vorwärts und Zurück oder spiele sie automatisch ab.",
|
||||
btnSave: "Speichern",
|
||||
btnLoad: "Save-Points",
|
||||
btnLoad: "Laden",
|
||||
saveSidebarTitle: "Speicherpunkt",
|
||||
saveSidebarClose: "Schliessen",
|
||||
saveSidebarText: "Gib dem Spielstand einen klaren Namen und optional eine kurze Notiz.",
|
||||
@@ -221,8 +234,8 @@ const translations = {
|
||||
historyHeadStatus: "Status",
|
||||
historyHeadSaved: "Gespeichert",
|
||||
infoModalClose: "Schliessen",
|
||||
infoModalIntro: "Setze pro Reihe genau eine Figur auf das Brett. Eine gültige Lösung hat keine Konflikte.",
|
||||
infoModalModeHeading: "So funktioniert der aktuelle Modus",
|
||||
infoModalIntro: "Das Schachbrett besteht aus vielen quadratischen Feldern. Jedes Feld kann leer sein oder genau eine Figur enthalten. Deine Aufgabe ist es, Figuren auf das Brett zu setzen, ohne dass sie sich gegenseitig angreifen. Das Puzzle ist erst gelöst, wenn genau so viele Figuren auf dem Brett stehen wie das Brett Felder pro Seite hat, also zum Beispiel 8 Figuren auf einem 8x8-Brett.",
|
||||
infoModalModeHeading: "Grundregeln",
|
||||
infoModalTimeHeading: "Was zeigen Zeit und Schritte?",
|
||||
infoModalTimeText: "Der Schrittzähler erfasst deine Züge auf dem Brett. Die Zeit läuft für das aktuelle Spiel weiter, auch wenn du die Seite neu lädst. Beim Speichern werden Zeit und Schritte zusammen mit dem Save-Point abgelegt.",
|
||||
pieceListTitle: ({ modeLabel }) => `Positionen der ${modeLabel}`,
|
||||
@@ -250,15 +263,16 @@ const translations = {
|
||||
solverDiscardedManual: "Solver-Ablauf verworfen, weil das Brett manuell geändert wurde.",
|
||||
solverDiscardedStatus: "Solver-Ablauf verworfen. Bereite ihn bei Bedarf neu vor.",
|
||||
solvedPerfect: ({ duration }) => `Perfekt gelöst in ${duration}.`,
|
||||
animationReset: "Animations-Ablauf zurückgesetzt. Starte die Animation erneut oder gehe weiter mit Nächster Schritt.",
|
||||
animationPreparing: ({ modeLabel, size }) => `Berechne den Animations-Ablauf für ${modeLabel.toLowerCase()} auf ${size} x ${size}...`,
|
||||
animationReset: "Animations-Ablauf zurückgesetzt. Nutze die Schritt-Buttons am Brett oder starte die Animation erneut.",
|
||||
animationAtStart: "Anfang des Animations-Ablaufs erreicht.",
|
||||
animationPreparing: ({ modeLabel, size }) => `Lade den Animations-Ablauf für ${modeLabel.toLowerCase()} auf ${size} x ${size}...`,
|
||||
animationPreparingStatus: ({ size }) => `Animations-Solver berechnet die Schritte fuer ${size} x ${size}.`,
|
||||
noSolution: "Keine Lösung gefunden.",
|
||||
noSolutionStatus: "Keine Lösung fuer dieses Brett gefunden.",
|
||||
animationReady: ({ steps, queensMode }) => queensMode
|
||||
? `Animations-Ablauf bereit mit ${steps} Schritten. Nutze Nächster Schritt oder Animation abspielen, um das Backtracking zu verfolgen.`
|
||||
: `Animations-Ablauf bereit mit ${steps} Schritten. Nutze Nächster Schritt oder Animation abspielen.`,
|
||||
animationReadyStatus: "Animations-Solver bereit. Nutze Nächster Schritt oder Animation abspielen.",
|
||||
? `Animations-Ablauf bereit mit ${steps} Schritten. Nutze die Vor- und Zurück-Buttons am Brett oder spiele die Animation ab, um das Backtracking zu verfolgen.`
|
||||
: `Animations-Ablauf bereit mit ${steps} Schritten. Nutze die Vor- und Zurück-Buttons am Brett oder spiele die Animation ab.`,
|
||||
animationReadyStatus: "Animations-Solver bereit. Nutze die Schritt-Buttons am Brett oder spiele die Animation ab.",
|
||||
animationLoadError: "Der Solver-Ablauf konnte nicht geladen werden.",
|
||||
animationFinishedStatus: ({ size }) => `Loesung für ${size} x ${size} Schritt fuer Schritt aufgebaut.`,
|
||||
animationAlreadyFinished: "Der Animations-Ablauf ist bereits abgeschlossen.",
|
||||
@@ -300,11 +314,11 @@ const translations = {
|
||||
accountShortcutAria: "Open account and login settings",
|
||||
introKicker: "Puzzle Mode",
|
||||
introTitle: "Choose your mode and solve the board your way.",
|
||||
introText: "Switch between queens and rooks, use animation or instant solve, and open board settings anytime from the left side tab.",
|
||||
introText: "Switch between queens and rooks, use animation or instant solve, and open game settings anytime from the left side tab.",
|
||||
modeSwitchAria: "Game mode",
|
||||
btnQueens: "Queens",
|
||||
btnRooks: "Rooks",
|
||||
infoButton: "Rules & Help",
|
||||
infoButton: "How to play?",
|
||||
gameStatsAria: "Game status",
|
||||
stepsLabel: "Steps",
|
||||
timeLabel: "Time",
|
||||
@@ -318,24 +332,33 @@ const translations = {
|
||||
animationSolverText: "Prepare the solving path, step through it manually, or play the animation automatically.",
|
||||
instantSolverTitle: "Instant Solve",
|
||||
instantSolverText: "Load a valid solution directly, without intermediate steps or animation.",
|
||||
defaultSolverMessage: "Prepare a solving trace and play it back step by step.",
|
||||
defaultSolverMessage: "Load an animation and control it from the board or play it automatically.",
|
||||
solverReady: ({ modeLabel }) => `Solver for ${modeLabel} is ready. ${t("defaultSolverMessage")}`,
|
||||
solverSpeedLabel: "Animation speed",
|
||||
btnPrepareSolve: "Prepare animation",
|
||||
btnPrepareSolveLoading: "Preparing animation...",
|
||||
btnNextStep: "Next step",
|
||||
btnPrepareSolve: "Load animation",
|
||||
btnPrepareSolveLoading: "Loading animation...",
|
||||
btnPrevStep: "Back",
|
||||
btnNextStep: "Forward",
|
||||
btnPlayPause: "Play animation",
|
||||
btnPlayReplay: "Replay animation",
|
||||
btnPlayPauseRunning: "Pause animation",
|
||||
btnInstantSolve: "Solve instantly",
|
||||
btnReset: "Reset board",
|
||||
settingsTrigger: "Board settings",
|
||||
settingsSidebarTitle: "Board Settings",
|
||||
settingsTrigger: "Game settings",
|
||||
settingsSidebarTitle: "Game settings",
|
||||
settingsSidebarClose: "Close",
|
||||
boardSettingsKicker: "Board",
|
||||
boardSettingsTitle: "Board size",
|
||||
settingsSidebarText: "Adjust the board size between 4 and 13.",
|
||||
boardSizeInputLabel: "Size",
|
||||
applyBoardSizeButton: "Apply size",
|
||||
settingsSidebarHint: "Changing the size resets the board for safety. On phones, 8x8 is the intended maximum for a reliable fit and larger boards may appear cropped.",
|
||||
saveSettingsKicker: "Saving",
|
||||
saveSettingsTitle: "Save progress",
|
||||
saveSettingsText: "Open the options for saving your progress or loading save points here.",
|
||||
solutionSettingsKicker: "Solution",
|
||||
solutionSettingsTitle: "Solving tools",
|
||||
solutionSettingsText: "Load an animation, control it from the board with back and forward, or play it automatically.",
|
||||
btnSave: "Save",
|
||||
btnLoad: "Save points",
|
||||
saveSidebarTitle: "Save Point",
|
||||
@@ -372,8 +395,8 @@ const translations = {
|
||||
historyHeadStatus: "Status",
|
||||
historyHeadSaved: "Saved",
|
||||
infoModalClose: "Close",
|
||||
infoModalIntro: "Place exactly one piece in each row. A valid solution has no conflicts.",
|
||||
infoModalModeHeading: "How the current mode works",
|
||||
infoModalIntro: "The chessboard is made of many square fields. Each field can either stay empty or hold exactly one piece. Your task is to place pieces on the board without letting them attack each other. The puzzle is only solved when the board contains exactly as many pieces as it has squares on one side, for example 8 pieces on an 8x8 board.",
|
||||
infoModalModeHeading: "Basic rules",
|
||||
infoModalTimeHeading: "What do time and steps show?",
|
||||
infoModalTimeText: "The step counter records your moves on the board. Time keeps running for the current game even if you reload the page. When saving, time and steps are stored together with the save point.",
|
||||
pieceListTitle: ({ modeLabel }) => `${modeLabel} positions`,
|
||||
@@ -401,15 +424,16 @@ const translations = {
|
||||
solverDiscardedManual: "Solver trace discarded because the board was changed manually.",
|
||||
solverDiscardedStatus: "Solver trace discarded. Prepare it again if needed.",
|
||||
solvedPerfect: ({ duration }) => `Perfectly solved in ${duration}.`,
|
||||
animationReset: "Animation trace reset. Start the animation again or continue with Next step.",
|
||||
animationPreparing: ({ modeLabel, size }) => `Preparing the animation trace for ${modeLabel.toLowerCase()} on ${size} x ${size}...`,
|
||||
animationReset: "Animation trace reset. Use the step buttons on the board or start the animation again.",
|
||||
animationAtStart: "Reached the start of the animation trace.",
|
||||
animationPreparing: ({ modeLabel, size }) => `Loading the animation trace for ${modeLabel.toLowerCase()} on ${size} x ${size}...`,
|
||||
animationPreparingStatus: ({ size }) => `Animation solver is preparing the steps for ${size} x ${size}.`,
|
||||
noSolution: "No solution found.",
|
||||
noSolutionStatus: "No solution found for this board.",
|
||||
animationReady: ({ steps, queensMode }) => queensMode
|
||||
? `Animation trace ready with ${steps} steps. Use Next step or Play animation to follow the backtracking process.`
|
||||
: `Animation trace ready with ${steps} steps. Use Next step or Play animation.`,
|
||||
animationReadyStatus: "Animation solver ready. Use Next step or Play animation.",
|
||||
? `Animation trace ready with ${steps} steps. Use the back and forward buttons on the board or play the animation to follow the backtracking process.`
|
||||
: `Animation trace ready with ${steps} steps. Use the back and forward buttons on the board or play the animation.`,
|
||||
animationReadyStatus: "Animation solver ready. Use the step buttons on the board or play the animation.",
|
||||
animationLoadError: "The solver trace could not be loaded.",
|
||||
animationFinishedStatus: ({ size }) => `Solution for ${size} x ${size} was built step by step.`,
|
||||
animationAlreadyFinished: "The animation trace is already complete.",
|
||||
@@ -525,7 +549,7 @@ const languageController = AppLanguage.createController({
|
||||
// Core data and formatting helpers
|
||||
|
||||
function createEmptyBoard(size) {
|
||||
return Array(size).fill(EMPTY_CELL);
|
||||
return Array.from({ length: size }, () => Array(size).fill(EMPTY_CELL));
|
||||
}
|
||||
|
||||
function clampBoardSize(size) {
|
||||
@@ -536,20 +560,71 @@ function getColumnLabel(index) {
|
||||
return String.fromCharCode(65 + index);
|
||||
}
|
||||
|
||||
function cloneBoard(selectedBoard) {
|
||||
return selectedBoard.map((row) => row.slice());
|
||||
}
|
||||
|
||||
function getCellKey(row, col) {
|
||||
return `${row}:${col}`;
|
||||
}
|
||||
|
||||
function sanitizeBoard(candidateBoard, size) {
|
||||
if (!Array.isArray(candidateBoard) || candidateBoard.length !== size) {
|
||||
return createEmptyBoard(size);
|
||||
}
|
||||
|
||||
return candidateBoard.map((value) => {
|
||||
if (candidateBoard.every((row) => Array.isArray(row) && row.length === size)) {
|
||||
return candidateBoard.map((row) => row.map((value) => {
|
||||
if (value === true) {
|
||||
return OCCUPIED_CELL;
|
||||
}
|
||||
|
||||
const numericValue = Number(value);
|
||||
return numericValue === OCCUPIED_CELL ? OCCUPIED_CELL : EMPTY_CELL;
|
||||
}));
|
||||
}
|
||||
|
||||
const normalizedBoard = createEmptyBoard(size);
|
||||
|
||||
candidateBoard.forEach((value, row) => {
|
||||
const numericValue = Number(value);
|
||||
|
||||
if (!Number.isInteger(numericValue)) {
|
||||
return EMPTY_CELL;
|
||||
if (Number.isInteger(numericValue) && numericValue >= 0 && numericValue < size) {
|
||||
normalizedBoard[row][numericValue] = OCCUPIED_CELL;
|
||||
}
|
||||
|
||||
return numericValue >= 0 && numericValue < size ? numericValue : EMPTY_CELL;
|
||||
});
|
||||
|
||||
return normalizedBoard;
|
||||
}
|
||||
|
||||
function getOccupiedPositions(selectedBoard = board) {
|
||||
const positions = [];
|
||||
|
||||
for (let row = 0; row < selectedBoard.length; row++) {
|
||||
for (let col = 0; col < selectedBoard[row].length; col++) {
|
||||
if (selectedBoard[row][col] === OCCUPIED_CELL) {
|
||||
positions.push({ row, col });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return positions;
|
||||
}
|
||||
|
||||
function extractSolverBoard(payload, size) {
|
||||
if (!Array.isArray(payload)) {
|
||||
return createEmptyBoard(size);
|
||||
}
|
||||
|
||||
if (payload.length === size) {
|
||||
return sanitizeBoard(payload, size);
|
||||
}
|
||||
|
||||
if (payload.length > 0) {
|
||||
return sanitizeBoard(payload[0], size);
|
||||
}
|
||||
|
||||
return createEmptyBoard(size);
|
||||
}
|
||||
|
||||
function formatDuration(totalSeconds) {
|
||||
@@ -745,23 +820,17 @@ function updateBoardLayout() {
|
||||
|
||||
function updateQueenList() {
|
||||
queenList.innerHTML = "";
|
||||
let count = 1;
|
||||
const positions = getOccupiedPositions();
|
||||
|
||||
for (let row = 0; row < boardSize; row++) {
|
||||
if (board[row] === EMPTY_CELL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const col = board[row];
|
||||
positions.forEach(({ row, col }, index) => {
|
||||
const position = `${getColumnLabel(col)}${boardSize - row}`;
|
||||
const li = document.createElement("li");
|
||||
|
||||
li.textContent = `${getPieceLabel(mode)} ${count}: ${position}`;
|
||||
li.textContent = `${getPieceLabel(mode)} ${index + 1}: ${position}`;
|
||||
queenList.appendChild(li);
|
||||
count += 1;
|
||||
}
|
||||
});
|
||||
|
||||
const placedPieces = count - 1;
|
||||
const placedPieces = positions.length;
|
||||
pieceCountBadge.textContent = t("pieceCount", { count: placedPieces });
|
||||
|
||||
if (placedPieces === 0) {
|
||||
@@ -797,11 +866,14 @@ function stopSolverPlayback(refreshControls = true) {
|
||||
|
||||
function updateSolverControls() {
|
||||
const currentStep = Math.max(0, solverStepIndex + 1);
|
||||
const hasTrace = hasPreparedSolverTrace();
|
||||
|
||||
btnPrepareSolve.disabled = solverLoading;
|
||||
btnInstantSolve.disabled = solverLoading;
|
||||
btnNextStep.disabled = solverLoading || hasCompletedSolverTrace();
|
||||
btnPrevStep.disabled = solverLoading || !hasTrace || solverStepIndex < 0;
|
||||
btnNextStep.disabled = solverLoading || !hasTrace || hasCompletedSolverTrace();
|
||||
btnPlayPause.disabled = solverLoading;
|
||||
boardNavigator.hidden = !hasTrace;
|
||||
|
||||
btnPrepareSolve.textContent = solverLoading ? t("btnPrepareSolveLoading") : t("btnPrepareSolve");
|
||||
|
||||
@@ -827,18 +899,39 @@ function discardSolverTrace(message = getDefaultSolverMessage()) {
|
||||
solverSteps = [];
|
||||
solverStepIndex = -1;
|
||||
currentSolverHighlight = null;
|
||||
solverInitialBoard = createEmptyBoard(boardSize);
|
||||
|
||||
setSolverMessage(message);
|
||||
updateSolverControls();
|
||||
}
|
||||
|
||||
function syncProgressAfterBoardStateChange() {
|
||||
if (isBoardSolved()) {
|
||||
finishSolvedRun();
|
||||
return;
|
||||
}
|
||||
|
||||
if (isSolved) {
|
||||
resetProgress({
|
||||
stepCountValue: stepCount,
|
||||
elapsedMs: getElapsedMilliseconds(),
|
||||
solved: false
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
updateProgressDisplay();
|
||||
persistCurrentSession();
|
||||
}
|
||||
|
||||
function rewindSolverTrace() {
|
||||
stopSolverPlayback(false);
|
||||
solverStepIndex = -1;
|
||||
currentSolverHighlight = null;
|
||||
board = createEmptyBoard(boardSize);
|
||||
board = sanitizeBoard(solverInitialBoard, boardSize);
|
||||
setSolverMessage(t("animationReset"));
|
||||
drawBoard();
|
||||
syncProgressAfterBoardStateChange();
|
||||
updateSolverControls();
|
||||
}
|
||||
|
||||
@@ -861,26 +954,24 @@ function setMode(newMode) {
|
||||
}
|
||||
|
||||
function getConflicts(selectedBoard = board, selectedMode = mode) {
|
||||
const conflicts = [];
|
||||
const conflicts = new Set();
|
||||
const positions = getOccupiedPositions(selectedBoard);
|
||||
|
||||
for (let rowA = 0; rowA < boardSize; rowA++) {
|
||||
if (selectedBoard[rowA] === EMPTY_CELL) {
|
||||
continue;
|
||||
}
|
||||
for (let indexA = 0; indexA < positions.length; indexA++) {
|
||||
const positionA = positions[indexA];
|
||||
|
||||
for (let rowB = rowA + 1; rowB < boardSize; rowB++) {
|
||||
if (selectedBoard[rowB] === EMPTY_CELL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const colA = selectedBoard[rowA];
|
||||
const colB = selectedBoard[rowB];
|
||||
const sameColumn = colA === colB;
|
||||
const sameDiagonal = Math.abs(colA - colB) === Math.abs(rowA - rowB);
|
||||
const hasConflict = selectedMode === "rooks" ? sameColumn : sameColumn || sameDiagonal;
|
||||
for (let indexB = indexA + 1; indexB < positions.length; indexB++) {
|
||||
const positionB = positions[indexB];
|
||||
const sameRow = positionA.row === positionB.row;
|
||||
const sameColumn = positionA.col === positionB.col;
|
||||
const sameDiagonal = Math.abs(positionA.col - positionB.col) === Math.abs(positionA.row - positionB.row);
|
||||
const hasConflict = selectedMode === "rooks"
|
||||
? sameRow || sameColumn
|
||||
: sameRow || sameColumn || sameDiagonal;
|
||||
|
||||
if (hasConflict) {
|
||||
conflicts.push([rowA, colA], [rowB, colB]);
|
||||
conflicts.add(getCellKey(positionA.row, positionA.col));
|
||||
conflicts.add(getCellKey(positionB.row, positionB.col));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -889,8 +980,7 @@ function getConflicts(selectedBoard = board, selectedMode = mode) {
|
||||
}
|
||||
|
||||
function isBoardSolved(selectedBoard = board, selectedMode = mode) {
|
||||
const allPlaced = selectedBoard.every((col) => col !== EMPTY_CELL);
|
||||
return allPlaced && getConflicts(selectedBoard, selectedMode).length === 0;
|
||||
return getPlacedPieceCount(selectedBoard) === boardSize && getConflicts(selectedBoard, selectedMode).size === 0;
|
||||
}
|
||||
|
||||
function finishSolvedRun() {
|
||||
@@ -940,10 +1030,10 @@ function drawBoard() {
|
||||
);
|
||||
}
|
||||
|
||||
if (board[row] === col) {
|
||||
if (board[row][col] === OCCUPIED_CELL) {
|
||||
cell.textContent = mode === "queens" ? "♛" : "♜";
|
||||
|
||||
if (conflicts.some(([conflictRow, conflictCol]) => conflictRow === row && conflictCol === col)) {
|
||||
if (conflicts.has(getCellKey(row, col))) {
|
||||
cell.classList.add("invalid");
|
||||
}
|
||||
}
|
||||
@@ -973,7 +1063,7 @@ function placeQueen(row, col) {
|
||||
}
|
||||
|
||||
currentSolverHighlight = null;
|
||||
board[row] = board[row] === col ? EMPTY_CELL : col;
|
||||
board[row][col] = board[row][col] === OCCUPIED_CELL ? EMPTY_CELL : OCCUPIED_CELL;
|
||||
stepCount += 1;
|
||||
drawBoard();
|
||||
updateProgressDisplay();
|
||||
@@ -1076,8 +1166,8 @@ function toggleInfoModal(forceOpen) {
|
||||
infoModal.setAttribute("aria-hidden", String(!shouldOpen));
|
||||
}
|
||||
|
||||
function getPlacedPieceCount() {
|
||||
return board.filter((col) => col !== EMPTY_CELL).length;
|
||||
function getPlacedPieceCount(selectedBoard = board) {
|
||||
return getOccupiedPositions(selectedBoard).length;
|
||||
}
|
||||
|
||||
function resetSaveForm() {
|
||||
@@ -1106,16 +1196,14 @@ function closeAllPanels() {
|
||||
// Save point and history helpers
|
||||
|
||||
function getBoardPreview(savedBoard, limit = 4) {
|
||||
const boardSizeForPreview = Array.isArray(savedBoard) ? savedBoard.length : DEFAULT_BOARD_SIZE;
|
||||
const normalizedBoard = sanitizeBoard(savedBoard, boardSizeForPreview);
|
||||
const preview = [];
|
||||
|
||||
for (let row = 0; row < savedBoard.length; row++) {
|
||||
if (savedBoard[row] === EMPTY_CELL) {
|
||||
continue;
|
||||
}
|
||||
for (const { row, col } of getOccupiedPositions(normalizedBoard)) {
|
||||
preview.push(`${getColumnLabel(col)}${boardSizeForPreview - row}`);
|
||||
|
||||
preview.push(`${getColumnLabel(savedBoard[row])}${savedBoard.length - row}`);
|
||||
|
||||
if (preview.length === limit) {
|
||||
if (preview.length >= limit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1314,7 +1402,7 @@ function applySolverStep(step) {
|
||||
|
||||
setSolverMessage(step.message || getDefaultSolverMessage());
|
||||
drawBoard();
|
||||
persistCurrentSession();
|
||||
syncProgressAfterBoardStateChange();
|
||||
updateSolverControls();
|
||||
}
|
||||
|
||||
@@ -1381,6 +1469,7 @@ async function prepareSolutionTrace() {
|
||||
const endpoint = mode === "rooks" ? "/solve_rooks_trace" : "/solve_trace";
|
||||
const requestId = ++solverRequestId;
|
||||
|
||||
toggleSidebar(false);
|
||||
solverLoading = true;
|
||||
stopSolverPlayback(false);
|
||||
solverSteps = [];
|
||||
@@ -1405,20 +1494,24 @@ async function prepareSolutionTrace() {
|
||||
|
||||
solverSteps = Array.isArray(trace.steps) ? trace.steps : [];
|
||||
solverStepIndex = -1;
|
||||
board = Array.isArray(trace.initial_board) ? trace.initial_board.slice() : createEmptyBoard(boardSize);
|
||||
solverInitialBoard = Array.isArray(trace.initial_board)
|
||||
? sanitizeBoard(trace.initial_board, boardSize)
|
||||
: createEmptyBoard(boardSize);
|
||||
board = cloneBoard(solverInitialBoard);
|
||||
currentSolverHighlight = null;
|
||||
|
||||
if (!trace.solved || solverSteps.length === 0) {
|
||||
setSolverMessage(t("noSolution"));
|
||||
setStatus(t("noSolutionStatus"), "red");
|
||||
drawBoard();
|
||||
persistCurrentSession();
|
||||
syncProgressAfterBoardStateChange();
|
||||
return false;
|
||||
}
|
||||
|
||||
setSolverMessage(t("animationReady", { steps: solverSteps.length, queensMode: mode === "queens" }));
|
||||
setStatus(t("animationReadyStatus"), "#2563eb");
|
||||
drawBoard();
|
||||
syncProgressAfterBoardStateChange();
|
||||
return true;
|
||||
} catch (error) {
|
||||
if (requestId !== solverRequestId) {
|
||||
@@ -1442,11 +1535,7 @@ async function nextSolverStep() {
|
||||
}
|
||||
|
||||
if (!hasPreparedSolverTrace()) {
|
||||
const prepared = await prepareSolutionTrace();
|
||||
|
||||
if (!prepared) {
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (isSolverPlaying()) {
|
||||
@@ -1458,6 +1547,35 @@ async function nextSolverStep() {
|
||||
}
|
||||
}
|
||||
|
||||
function previousSolverStep() {
|
||||
if (solverLoading || !hasPreparedSolverTrace()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isSolverPlaying()) {
|
||||
stopSolverPlayback(false);
|
||||
}
|
||||
|
||||
if (solverStepIndex < 0) {
|
||||
setStatus(t("animationAtStart"), "#2563eb");
|
||||
return;
|
||||
}
|
||||
|
||||
solverStepIndex -= 1;
|
||||
|
||||
if (solverStepIndex >= 0) {
|
||||
applySolverStep(solverSteps[solverStepIndex]);
|
||||
return;
|
||||
}
|
||||
|
||||
currentSolverHighlight = null;
|
||||
board = sanitizeBoard(solverInitialBoard, boardSize);
|
||||
setSolverMessage(t("animationAtStart"));
|
||||
drawBoard();
|
||||
syncProgressAfterBoardStateChange();
|
||||
updateSolverControls();
|
||||
}
|
||||
|
||||
async function toggleSolverPlayback() {
|
||||
if (solverLoading) {
|
||||
return;
|
||||
@@ -1497,11 +1615,13 @@ async function instantSolve() {
|
||||
const endpoint = mode === "rooks" ? "/solve_rooks" : "/solve";
|
||||
const requestId = ++solverRequestId;
|
||||
|
||||
toggleSidebar(false);
|
||||
solverLoading = true;
|
||||
stopSolverPlayback(false);
|
||||
solverSteps = [];
|
||||
solverStepIndex = -1;
|
||||
currentSolverHighlight = null;
|
||||
solverInitialBoard = createEmptyBoard(boardSize);
|
||||
setSolverMessage(t("instantLoading", { modeLabel: getModeLabel(mode), size: boardSize }));
|
||||
setStatus(t("instantLoadingStatus"), "#2563eb");
|
||||
updateSolverControls();
|
||||
@@ -1519,7 +1639,7 @@ async function instantSolve() {
|
||||
return;
|
||||
}
|
||||
|
||||
board = mode === "queens" && Array.isArray(solution[0]) ? solution[0].slice() : solution.slice();
|
||||
board = extractSolverBoard(solution, boardSize);
|
||||
currentSolverHighlight = null;
|
||||
finishSolvedRun();
|
||||
setSolverMessage(t("instantLoaded"));
|
||||
|
||||
+26
-27
@@ -11,55 +11,55 @@ let currentLanguage = initialLanguage;
|
||||
const translations = {
|
||||
de: {
|
||||
"meta.title": "8-Damen- und 8-Türme Solver",
|
||||
"nav.brand": "8-Damen- und 8-Türme Solver",
|
||||
"nav.brand": "8-Damen- und 8-Türme-Löser",
|
||||
"nav.problem": "Problem",
|
||||
"nav.features": "Features",
|
||||
"nav.features": "Funktionen",
|
||||
"nav.journey": "Ablauf",
|
||||
"nav.login": "Login",
|
||||
"nav.signup": "Create Account",
|
||||
"nav.login": "Anmelden",
|
||||
"nav.signup": "Account erstellen",
|
||||
"hero.eyebrow": "Interaktives Lernen mit Strategie, Logik und Backtracking",
|
||||
"hero.title": "Entdecke das Acht-Damen- und Acht-Türme-Problem.",
|
||||
"hero.text": "Diese App macht aus einem klassischen Informatik- und Mathematikproblem ein interaktives Erlebnis: Setze Damen oder Türme, verfolge den Solver Schritt fuer Schritt, miss deine Zeit und speichere clevere Zwischenstände.",
|
||||
"hero.text": "Diese App macht aus einem klassischen Informatik- und Mathematikproblem ein interaktives Erlebnis: Setze Damen oder Türme, verfolge den Solver Schritt für Schritt, miss deine Zeit und speichere clevere Zwischenstände.",
|
||||
"hero.primaryCta": "Jetzt starten",
|
||||
"hero.secondaryCta": "Bereits Account? Login",
|
||||
"hero.secondaryCta": "Bereits ein Account? Anmelden",
|
||||
"hero.fact1Title": "Skalierbar",
|
||||
"hero.fact1Text": "Brettgrösse flexibel anpassen",
|
||||
"hero.fact2Title": "Schritt für Schritt",
|
||||
"hero.fact2Text": "Solver mit Animation",
|
||||
"hero.fact3Title": "Save-Points",
|
||||
"hero.fact3Title": "Speicherstände",
|
||||
"hero.fact3Text": "Zeit und Fortschritt behalten",
|
||||
"hero.solverLabel": "Solver",
|
||||
"hero.solverValue": "Backtracking live",
|
||||
"hero.challengeLabel": "Challenge",
|
||||
"hero.solverLabel": "Lösungshelfer",
|
||||
"hero.solverValue": "Backtracking in Echtzeit",
|
||||
"hero.challengeLabel": "Herausforderung",
|
||||
"hero.challengeValue": "Zeit und Schritte messen",
|
||||
"problem.eyebrow": "Warum dieses Problem spannend ist",
|
||||
"problem.title": "Die Acht-Damen- und Acht-Türme-Probleme sind mehr als Schachrätsel.",
|
||||
"problem.text": "Beim Acht-Damen-Problem suchst du eine Anordnung von acht Damen auf einem Schachbrett, sodass sich keine zwei Damen schlagen. Beim Acht-Tuerme-Problem gilt dieselbe Idee für Türme, nur mit einfacheren Konfliktregeln. Beide Aufgaben zeigen, wie Mustererkennung, systematische Suche und Backtracking funktionieren.<br><br>Gerade beim Damenproblem wird schnell sichtbar, warum Strategie wichtig ist: Es gibt unzählige moegliche Platzierungen, aber nur sehr wenige wirklich gültige Lösungen. Genau das macht diese App spannend, weil man nicht nur das Ergebnis sieht, sondern auch den Weg dorthin.",
|
||||
"problem.eyebrow": "Was ist das Acht-Damen-Problem?",
|
||||
"problem.title": "Eine klassische schachmathematische Aufgabe mit überraschend vielen Lösungen.",
|
||||
"problem.text": "Das Acht-Damen-Problem ist eine schachmathematische Aufgabe: Acht Damen sollen auf einem Schachbrett so aufgestellt werden, dass keine zwei Damen dieselbe Reihe, Spalte oder Diagonale teilen. Das Problem lässt sich auf beliebige Brettgrössen verallgemeinern, indem n Damen auf einem n x n-Brett platziert werden.<br><br>Für das klassische 8x8-Brett gibt es 92 Lösungen. Zählt man Stellungen, die nur durch Spiegelung oder Drehung entstehen, als gleich, bleiben 12 eindeutige Lösungen übrig.<br><br><a href=\"https://de-academic.com/dic.nsf/dewiki/13248\" target=\"_blank\" rel=\"noopener noreferrer\">Quelle: de-academic.com</a>",
|
||||
"features.eyebrow": "Was die Applikation bietet",
|
||||
"features.title": "Von der Idee bis zur gelösten Stellung.",
|
||||
"features.card1Title": "Interaktives Brett",
|
||||
"features.card1Text": "Setze Damen oder Türme direkt auf dem Brett und prüfe, ob deine Anordnung konfliktfrei ist.",
|
||||
"features.card2Title": "Solver mit Animation",
|
||||
"features.card2Text": "Beobachte den Lösungsweg Schritt fuer Schritt und verstehe, wie Backtracking arbeitet.",
|
||||
"features.card2Text": "Beobachte den Lösungsweg Schritt für Schritt und verstehe, wie Backtracking arbeitet.",
|
||||
"features.card3Title": "Zeit und Schritte",
|
||||
"features.card3Text": "Miss deinen Fortschritt während des Spiels und vergleiche unterschiedliche Versuche.",
|
||||
"features.card4Title": "Save-Points",
|
||||
"features.card4Text": "Speichere Zwischenstände mit Notizen, Zeiten und Schrittzahl fuer späteres Weiterarbeiten.",
|
||||
"features.card4Title": "Speicherstände",
|
||||
"features.card4Text": "Speichere Zwischenstände mit Notizen, Zeiten und Schrittzahl für späteres Weiterarbeiten.",
|
||||
"features.card5Title": "Skalierbare Bretter",
|
||||
"features.card5Text": "Passe die Brettgrösse an und erkunde die Probleme nicht nur auf dem klassischen 8x8-Brett.",
|
||||
"journey.eyebrow": "So funktioniert dein Einstieg",
|
||||
"journey.title": "In wenigen Schritten vom ersten Besuch zur eigenen Lösung.",
|
||||
"journey.step1Title": "Account erstellen",
|
||||
"journey.step1Text": "Registriere dich und erhalte Zugang zu Dashboard, Save-Points und deinem Fortschritt.",
|
||||
"journey.step1Text": "Registriere dich und erhalte Zugang zum Dashboard, zu Speicherständen und zu deinem Fortschritt.",
|
||||
"journey.step2Title": "Spielmodus wählen",
|
||||
"journey.step2Text": "Arbeite mit Damen oder Türmen und passe die Brettgrösse deinem Ziel an.",
|
||||
"journey.step3Title": "Lösen oder analysieren",
|
||||
"journey.step3Text": "Teste eigene Ideen oder lass dir die Lösung visuell vom Solver lösen lassen, dabei wird 1:1 gezeigt wie der Solver vorgeht.",
|
||||
"journey.step3Text": "Teste eigene Ideen oder lass dir die Lösung visuell vom Solver zeigen, damit du den Lösungsweg besser nachvollziehen kannst.",
|
||||
"cta.eyebrow": "Bereit für den ersten Versuch?",
|
||||
"cta.title": "Mach aus einem bekannten Problem ein sichtbares Lernerlebnis.",
|
||||
"cta.text": "Starte mit einem Account, speichere deine Zwischenstände und finde heraus, wie schnell du eine konfliktfreie Lösung erreichst.",
|
||||
"cta.primaryCta": "Create Account",
|
||||
"cta.secondaryCta": "Login",
|
||||
"cta.primaryCta": "Account erstellen",
|
||||
"cta.secondaryCta": "Anmelden",
|
||||
"footerText": "Schulprojekt rund um das Acht-Damen- und Acht-Türme-Problem.",
|
||||
"footerPrivacy": "Datenschutzerklärung",
|
||||
"footerImprint": "Impressum",
|
||||
@@ -71,13 +71,13 @@ const translations = {
|
||||
"nav.problem": "Problem",
|
||||
"nav.features": "Features",
|
||||
"nav.journey": "Journey",
|
||||
"nav.login": "Login",
|
||||
"nav.login": "Sign in",
|
||||
"nav.signup": "Create Account",
|
||||
"hero.eyebrow": "Interactive learning with strategy, logic, and backtracking",
|
||||
"hero.title": "Discover the Eight Queens and Eight Rooks puzzle.",
|
||||
"hero.text": "This app turns a classic computer science and mathematics problem into an interactive experience: place queens or rooks, follow the solver step by step, track your time, and save clever checkpoints.",
|
||||
"hero.primaryCta": "Get Started",
|
||||
"hero.secondaryCta": "Already have an account? Login",
|
||||
"hero.secondaryCta": "Already have an account? Sign in",
|
||||
"hero.fact1Title": "Scalable",
|
||||
"hero.fact1Text": "Adjust the board size freely",
|
||||
"hero.fact2Title": "Step by step",
|
||||
@@ -88,9 +88,9 @@ const translations = {
|
||||
"hero.solverValue": "Backtracking in motion",
|
||||
"hero.challengeLabel": "Challenge",
|
||||
"hero.challengeValue": "Measure time and steps",
|
||||
"problem.eyebrow": "Why this puzzle matters",
|
||||
"problem.title": "The Eight Queens and Eight Rooks problems are more than chess puzzles.",
|
||||
"problem.text": "In the Eight Queens problem, you search for a way to place eight queens on a chessboard so that no two queens can attack each other. The Eight Rooks problem follows the same idea for rooks, but with simpler conflict rules. Both tasks show how pattern recognition, systematic search, and backtracking work.<br><br>The queens puzzle quickly makes it obvious why strategy matters: there are countless possible placements, but only a few truly valid solutions. That is what makes this app interesting, because you do not just see the result, you also see the path to it.",
|
||||
"problem.eyebrow": "What is the Eight Queens problem?",
|
||||
"problem.title": "A classic chess-related math puzzle with surprisingly many solutions.",
|
||||
"problem.text": "The Eight Queens problem is a chess-related mathematical puzzle: eight queens must be placed on a chessboard so that no two queens share the same row, column, or diagonal. The idea can be generalized to boards of any size by placing n queens on an n x n board.<br><br>On the classic 8x8 board there are 92 solutions. If positions that only differ by reflection or rotation are counted as the same, 12 unique solutions remain.<br><br><a href=\"https://de-academic.com/dic.nsf/dewiki/13248\" target=\"_blank\" rel=\"noopener noreferrer\">Source: de-academic.com</a>",
|
||||
"features.eyebrow": "What the application offers",
|
||||
"features.title": "From the first idea to a solved position.",
|
||||
"features.card1Title": "Interactive board",
|
||||
@@ -115,7 +115,7 @@ const translations = {
|
||||
"cta.title": "Turn a famous puzzle into a visible learning experience.",
|
||||
"cta.text": "Start with an account, save your checkpoints, and find out how quickly you can reach a conflict-free solution.",
|
||||
"cta.primaryCta": "Create Account",
|
||||
"cta.secondaryCta": "Login",
|
||||
"cta.secondaryCta": "Sign in",
|
||||
"footerText": "School project about the Eight Queens and Eight Rooks problem.",
|
||||
"footerPrivacy": "Privacy Policy",
|
||||
"footerImprint": "Imprint",
|
||||
@@ -210,7 +210,6 @@ function rotatePattern() {
|
||||
|
||||
createHeroBoard();
|
||||
applyPattern(queenPatterns[currentPatternIndex]);
|
||||
languageController.init();
|
||||
|
||||
if (heroBoard) {
|
||||
window.setInterval(rotatePattern, 2200);
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
<button class="settings-trigger" id="settingsTrigger" onclick="toggleSidebar()" type="button"
|
||||
aria-controls="settingsSidebar" aria-expanded="false">
|
||||
<span class="settings-trigger-arrow" id="settingsTriggerArrow">→</span>
|
||||
<span class="settings-trigger-label" id="settingsTriggerLabel">Brett-Einstellungen</span>
|
||||
<span class="settings-trigger-label" id="settingsTriggerLabel">Spiel-Einstellungen</span>
|
||||
</button>
|
||||
|
||||
<div class="container">
|
||||
@@ -65,14 +65,14 @@
|
||||
</p>
|
||||
|
||||
<div class="mode-switch" data-i18n-aria-label="modeSwitchAria" aria-label="Spielmodus">
|
||||
<button class="btn active" onclick="setMode('queens')" id="btnQueens" type="button">
|
||||
<span aria-hidden="true">♛</span>
|
||||
<span data-i18n="btnQueens">Damen</span>
|
||||
</button>
|
||||
<button class="btn secondary" onclick="setMode('rooks')" id="btnRooks" type="button">
|
||||
<span aria-hidden="true">♜</span>
|
||||
<span data-i18n="btnRooks">Türme</span>
|
||||
</button>
|
||||
<button class="btn active" onclick="setMode('queens')" id="btnQueens" type="button">
|
||||
<span aria-hidden="true">♛</span>
|
||||
<span data-i18n="btnQueens">Damen</span>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -82,13 +82,14 @@
|
||||
<p class="section-kicker" id="gameModeEyebrow">Damen-Modus</p>
|
||||
<h2 id="gameTitle">Damen-Spielbrett</h2>
|
||||
<p class="board-mode-text" id="boardModeText">
|
||||
Platziere Damen so, dass keine zwei dieselbe Spalte oder Diagonale teilen.
|
||||
Platziere Damen frei auf dem Brett. Konflikte entstehen in derselben Reihe, Spalte oder
|
||||
Diagonale.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<button class="info-button" id="infoButton" onclick="toggleInfoModal(true)" type="button"
|
||||
data-i18n="infoButton" data-i18n-aria-label="infoButton"
|
||||
aria-label="Spielregeln und Hilfe anzeigen">Regeln & Hilfe</button>
|
||||
aria-label="Spielregeln und Hilfe anzeigen">Spielanleitung</button>
|
||||
</div>
|
||||
|
||||
<p class="board-size-info" id="boardSizeInfo"><span id="boardSizeInfoPrefix">Aktuelle Brettgrösse im
|
||||
@@ -118,69 +119,31 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="board" id="board"></div>
|
||||
<div class="board-stage">
|
||||
<div class="board-shell">
|
||||
<div id="board"></div>
|
||||
</div>
|
||||
|
||||
<div class="board-navigator" id="boardNavigator" hidden>
|
||||
<button class="btn secondary board-nav-button" onclick="previousSolverStep()" id="btnPrevStep"
|
||||
type="button" data-i18n="btnPrevStep">
|
||||
Zurück
|
||||
</button>
|
||||
<p class="solver-step-info" id="solverStepInfo">Schritt 0 / 0</p>
|
||||
<button class="btn secondary board-nav-button" onclick="nextSolverStep()" id="btnNextStep"
|
||||
type="button" data-i18n="btnNextStep">
|
||||
Vorwärts
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p class="solver-message solver-message-inline" id="solverMessage">Lade eine Animation und steuere sie
|
||||
am Brett oder spiele sie automatisch ab.</p>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<button class="btn secondary" onclick="resetBoard()" id="btnReset" type="button"
|
||||
data-i18n="btnReset">Brett
|
||||
zurücksetzen</button>
|
||||
<div class="controls-save-row">
|
||||
<button class="btn" onclick="openSavePanel()" id="btnSave" type="button"
|
||||
data-i18n="btnSave">Speichern</button>
|
||||
<button class="btn secondary" onclick="openSavePointsPanel()" id="btnLoad" type="button"
|
||||
data-i18n="btnLoad">Save-Points</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="solver-panel">
|
||||
<div class="solver-panel-header">
|
||||
<div>
|
||||
<p class="section-kicker" id="solverKicker" data-i18n="solverKicker">Solver</p>
|
||||
<h3 id="solverTitle" data-i18n="solverTitle">Lösungswerkzeuge</h3>
|
||||
<p class="solver-message" id="solverMessage">Bereite einen Ablauf vor und spiele ihn Schritt
|
||||
für Schritt ab.</p>
|
||||
</div>
|
||||
<p class="solver-step-info" id="solverStepInfo">Schritt 0 / 0</p>
|
||||
</div>
|
||||
|
||||
<div class="solver-groups">
|
||||
<section class="solver-group">
|
||||
<h4 id="animationSolverTitle" data-i18n="animationSolverTitle">Animations-Solver</h4>
|
||||
<p class="solver-group-text" id="animationSolverText" data-i18n="animationSolverText">
|
||||
Bereite den Lösungsweg vor, gehe ihn manüll Schritt für Schritt durch oder spiele die
|
||||
Animation automatisch ab.
|
||||
</p>
|
||||
|
||||
<div class="solver-controls">
|
||||
<button class="btn" onclick="prepareSolutionTrace()" id="btnPrepareSolve" type="button"
|
||||
data-i18n="btnPrepareSolve">Animation vorbereiten</button>
|
||||
<button class="btn secondary" onclick="nextSolverStep()" id="btnNextStep" type="button"
|
||||
data-i18n="btnNextStep">Nächster Schritt</button>
|
||||
<button class="btn secondary" onclick="toggleSolverPlayback()" id="btnPlayPause"
|
||||
type="button" data-i18n="btnPlayPause">Animation abspielen</button>
|
||||
</div>
|
||||
|
||||
<div class="solver-speed">
|
||||
<label class="solver-speed-label" id="solverSpeedLabel" for="solverSpeedInput"
|
||||
data-i18n="solverSpeedLabel">Animationsgeschwindigkeit</label>
|
||||
<input id="solverSpeedInput" type="range" min="80" max="900" step="20" value="320"
|
||||
oninput="updateSolverSpeed(this.value)">
|
||||
<span class="solver-speed-value" id="solverSpeedValue">320 ms</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="solver-group instant-solver-group">
|
||||
<h4 id="instantSolverTitle" data-i18n="instantSolverTitle">Sofort-Lösung</h4>
|
||||
<p class="solver-group-text" id="instantSolverText" data-i18n="instantSolverText">
|
||||
Lade direkt eine gültige Lösung, ohne Zwischenschritte oder Animation.
|
||||
</p>
|
||||
|
||||
<div class="instant-solver-actions">
|
||||
<button class="btn accent" onclick="instantSolve()" id="btnInstantSolve" type="button"
|
||||
data-i18n="btnInstantSolve">Sofort lösen</button>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -234,25 +197,68 @@
|
||||
|
||||
<aside class="sidebar settings-sidebar" id="settingsSidebar" aria-hidden="true">
|
||||
<div class="sidebar-header">
|
||||
<h3 id="settingsSidebarTitle" data-i18n="settingsSidebarTitle">Brett-Einstellungen</h3>
|
||||
<h3 id="settingsSidebarTitle" data-i18n="settingsSidebarTitle">Spiel-Einstellungen</h3>
|
||||
<button class="btn secondary sidebar-close" onclick="toggleSidebar(false)" id="settingsSidebarClose"
|
||||
type="button" data-i18n="settingsSidebarClose">Schliessen</button>
|
||||
</div>
|
||||
|
||||
<p id="settingsSidebarText" data-i18n="settingsSidebarText">Passe die Brettgrösse zwischen 4 und 13 an.</p>
|
||||
<section class="settings-section">
|
||||
<p class="section-kicker" id="boardSettingsKicker" data-i18n="boardSettingsKicker">Brett</p>
|
||||
<h4 id="boardSettingsTitle" data-i18n="boardSettingsTitle">Brettgrösse</h4>
|
||||
<p id="settingsSidebarText" data-i18n="settingsSidebarText">Passe die Brettgrösse zwischen 4 und 13 an.</p>
|
||||
|
||||
<label class="sidebar-label" id="boardSizeInputLabel" for="boardSizeInput"
|
||||
data-i18n="boardSizeInputLabel">Grösse</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>
|
||||
<label class="sidebar-label" id="boardSizeInputLabel" for="boardSizeInput"
|
||||
data-i18n="boardSizeInputLabel">Grösse</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()" id="applyBoardSizeButton" type="button"
|
||||
data-i18n="applyBoardSizeButton">Grösse anwenden</button>
|
||||
<p class="sidebar-hint" id="settingsSidebarHint" data-i18n="settingsSidebarHint">Beim Ändern wird das Brett zur
|
||||
Sicherheit zurückgesetzt. Auf
|
||||
dem Handy ist 8x8 eingeplant, grössere Bretter können abgeschnitten wirken.</p>
|
||||
<button class="btn" onclick="applyBoardSize()" id="applyBoardSizeButton" type="button"
|
||||
data-i18n="applyBoardSizeButton">Grösse anwenden</button>
|
||||
<p class="sidebar-hint" id="settingsSidebarHint" data-i18n="settingsSidebarHint">Beim Ändern wird das Brett
|
||||
zur Sicherheit zurückgesetzt. Auf
|
||||
dem Handy ist 8x8 eingeplant, grössere Bretter können abgeschnitten wirken.</p>
|
||||
</section>
|
||||
|
||||
<section class="settings-section">
|
||||
<p class="section-kicker" id="saveSettingsKicker" data-i18n="saveSettingsKicker">Speichern</p>
|
||||
<h4 id="saveSettingsTitle" data-i18n="saveSettingsTitle">Fortschritt speichern</h4>
|
||||
<p id="saveSettingsText" data-i18n="saveSettingsText">Öffne hier die Optionen zum Speichern oder Laden
|
||||
deiner Save-Points.</p>
|
||||
|
||||
<div class="settings-action-grid">
|
||||
<button class="btn" onclick="openSavePanel()" id="btnSave" type="button"
|
||||
data-i18n="btnSave">Speichern</button>
|
||||
<button class="btn secondary" onclick="openSavePointsPanel()" id="btnLoad" type="button"
|
||||
data-i18n="btnLoad">Save-Points</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="settings-section">
|
||||
<p class="section-kicker" id="solutionSettingsKicker" data-i18n="solutionSettingsKicker">Lösung</p>
|
||||
<h4 id="solutionSettingsTitle" data-i18n="solutionSettingsTitle">Lösungswerkzeuge</h4>
|
||||
<p id="solutionSettingsText" data-i18n="solutionSettingsText">Lade eine Animation, steuere sie am Brett mit
|
||||
Vorwärts und Zurück oder spiele sie automatisch ab.</p>
|
||||
|
||||
<div class="settings-action-grid">
|
||||
<button class="btn" onclick="prepareSolutionTrace()" id="btnPrepareSolve" type="button"
|
||||
data-i18n="btnPrepareSolve">Animation laden</button>
|
||||
<button class="btn secondary" onclick="toggleSolverPlayback()" id="btnPlayPause" type="button"
|
||||
data-i18n="btnPlayPause">Animation abspielen</button>
|
||||
<button class="btn accent" onclick="instantSolve()" id="btnInstantSolve" type="button"
|
||||
data-i18n="btnInstantSolve">Sofort lösen</button>
|
||||
</div>
|
||||
|
||||
<div class="solver-speed">
|
||||
<label class="solver-speed-label" id="solverSpeedLabel" for="solverSpeedInput"
|
||||
data-i18n="solverSpeedLabel">Animationsgeschwindigkeit</label>
|
||||
<input id="solverSpeedInput" type="range" min="80" max="900" step="20" value="320"
|
||||
oninput="updateSolverSpeed(this.value)">
|
||||
<span class="solver-speed-value" id="solverSpeedValue">320 ms</span>
|
||||
</div>
|
||||
</section>
|
||||
</aside>
|
||||
|
||||
<aside class="sidebar save-sidebar" id="saveSidebar" aria-hidden="true">
|
||||
@@ -338,23 +344,27 @@
|
||||
<div class="modal-backdrop" id="infoModalBackdrop" onclick="toggleInfoModal(false)"></div>
|
||||
<section class="modal" id="infoModal" aria-hidden="true" aria-labelledby="infoModalTitle">
|
||||
<div class="modal-header">
|
||||
<h3 id="infoModalTitle">Spielregeln und Acht-Damen-Problem</h3>
|
||||
<h3 id="infoModalTitle">Spielanleitung</h3>
|
||||
<button class="btn secondary sidebar-close" onclick="toggleInfoModal(false)" type="button"
|
||||
data-i18n="infoModalClose">Schliessen</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-content">
|
||||
<p data-i18n="infoModalIntro">Setze pro Reihe genau eine Figur auf das Brett. Eine gültige Lösung hat keine
|
||||
Konflikte.</p>
|
||||
<p data-i18n="infoModalIntro">Das Schachbrett besteht aus vielen quadratischen Feldern. Jedes Feld kann
|
||||
leer sein oder genau eine Figur enthalten. Deine Aufgabe ist es, Figuren auf das Brett zu setzen,
|
||||
ohne dass sie sich gegenseitig angreifen. Das Puzzle ist erst gelöst, wenn genau so viele Figuren auf
|
||||
dem Brett stehen wie das Brett Felder pro Seite hat, also zum Beispiel 8 Figuren auf einem
|
||||
8x8-Brett.</p>
|
||||
|
||||
<h4 data-i18n="infoModalModeHeading">So funktioniert der aktuelle Modus</h4>
|
||||
<p id="infoModeRuleText">Im Damen-Modus dürfen sich Damen weder in derselben Spalte noch diagonal
|
||||
angreifen. Mit einem Klick setzt oder entfernst du eine Figur.</p>
|
||||
<h4 data-i18n="infoModalModeHeading">Grundregeln</h4>
|
||||
<p id="infoModeRuleText">In diesem Modus spielst du mit Damen. Eine Dame ist die Figur mit dem Symbol ♛.
|
||||
Sie darf keine andere Dame in derselben waagerechten Reihe, senkrechten Spalte oder schrägen
|
||||
Diagonale treffen. Mit einem Klick setzt du eine Dame auf ein freies Feld oder entfernst sie wieder.</p>
|
||||
|
||||
<h4 id="infoModalProblemHeading">Was ist das Acht-Damen-Problem?</h4>
|
||||
<p id="infoModalProblemText">Das klassische Acht-Damen-Problem fragt nach einer Anordnung von acht Damen
|
||||
auf einem 8x8-Brett, bei der sich keine zwei Damen schlagen. Es ist ein bekanntes Beispiel für
|
||||
kombinatorische Suche und Backtracking.</p>
|
||||
<h4 id="infoModalProblemHeading">Was ist eine Dame?</h4>
|
||||
<p id="infoModalProblemText">Eine Dame ist eine Schachfigur. In diesem Spiel musst du keine Schachregeln
|
||||
lernen, sondern nur wissen: Eine Dame kann gerade und schräg über das Brett wirken. Darum müssen
|
||||
Damen genug Abstand haben, damit sie sich nicht gegenseitig erreichen.</p>
|
||||
|
||||
<h4 data-i18n="infoModalTimeHeading">Was zeigen Zeit und Schritte?</h4>
|
||||
<p data-i18n="infoModalTimeText">Der Schrittzähler erfasst deine Züge auf dem Brett. Die Zeit läuft für das
|
||||
|
||||
+34
-45
@@ -4,7 +4,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title data-i18n="meta.title">8-Damen- und 8-Tuerme Solver</title>
|
||||
<title data-i18n="meta.title">8-Damen- und 8-Türme Solver</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/index.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/footer.css') }}">
|
||||
</head>
|
||||
@@ -13,16 +13,16 @@
|
||||
<header class="landing-nav">
|
||||
<a class="brand" href="{{ url_for('main.home') }}">
|
||||
<span class="brand-mark">♛</span>
|
||||
<span data-i18n="nav.brand">8-Damen- und 8-Tuerme Solver</span>
|
||||
<span data-i18n="nav.brand">8-Damen- und 8-Türme-Löser</span>
|
||||
</a>
|
||||
|
||||
<nav class="landing-links">
|
||||
<a href="#problem" data-i18n="nav.problem">Problem</a>
|
||||
<a href="#features" data-i18n="nav.features">Features</a>
|
||||
<a href="#features" data-i18n="nav.features">Funktionen</a>
|
||||
<a href="#journey" data-i18n="nav.journey">Ablauf</a>
|
||||
<button class="lang-switch" id="langSwitch" type="button" aria-label="Sprache wechseln">DE</button>
|
||||
<a class="nav-cta secondary" href="{{ url_for('login.login') }}" data-i18n="nav.login">Login</a>
|
||||
<a class="nav-cta" href="{{ url_for('login.register') }}" data-i18n="nav.signup">Create Account</a>
|
||||
<a class="nav-cta secondary" href="{{ url_for('login.login') }}" data-i18n="nav.login">Anmelden</a>
|
||||
<a class="nav-cta" href="{{ url_for('login.register') }}" data-i18n="nav.signup">Account erstellen</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
@@ -33,42 +33,28 @@
|
||||
</p>
|
||||
<h1 data-i18n="hero.title">Entdecke das Acht-Damen- und Acht-Türme-Problem.</h1>
|
||||
<p class="hero-text" data-i18n="hero.text">
|
||||
Diese App macht aus einem klassischen Informatik- und Mathematikproblem ein interaktives Erlebnis:
|
||||
Diese Webanwendung macht aus einem klassischen Informatik- und Mathematikproblem ein interaktives
|
||||
Erlebnis:
|
||||
Setze Damen oder Türme, verfolge den Solver Schritt für Schritt, miss deine Zeit und speichere
|
||||
clevere Zwischenstände.
|
||||
Zwischenstände.
|
||||
</p>
|
||||
|
||||
<div class="hero-actions">
|
||||
<a class="hero-button primary" href="{{ url_for('login.register') }}"
|
||||
data-i18n="hero.primaryCta">Jetzt starten</a>
|
||||
<a class="hero-button secondary" href="{{ url_for('login.login') }}"
|
||||
data-i18n="hero.secondaryCta">Bereits Account? Login</a>
|
||||
</div>
|
||||
|
||||
<div class="hero-facts">
|
||||
<article>
|
||||
<strong data-i18n="hero.fact1Title">Skalierbar</strong>
|
||||
<span data-i18n="hero.fact1Text">Brettgrösse flexibel anpassen</span>
|
||||
</article>
|
||||
<article>
|
||||
<strong data-i18n="hero.fact2Title">Schritt für Schritt</strong>
|
||||
<span data-i18n="hero.fact2Text">Solver mit Animation</span>
|
||||
</article>
|
||||
<article>
|
||||
<strong data-i18n="hero.fact3Title">Save-Points</strong>
|
||||
<span data-i18n="hero.fact3Text">Zeit und Fortschritt behalten</span>
|
||||
</article>
|
||||
data-i18n="hero.secondaryCta">Bereits ein Account? Anmelden</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hero-visual">
|
||||
<div class="board-showcase" id="heroBoard" aria-hidden="true"></div>
|
||||
<div class="floating-card solver-card">
|
||||
<p class="floating-label" data-i18n="hero.solverLabel">Solver</p>
|
||||
<p class="floating-value" data-i18n="hero.solverValue">Backtracking live</p>
|
||||
<p class="floating-label" data-i18n="hero.solverLabel">Lösungshelfer</p>
|
||||
<p class="floating-value" data-i18n="hero.solverValue">Backtracking in Echtzeit</p>
|
||||
</div>
|
||||
<div class="floating-card timer-card">
|
||||
<p class="floating-label" data-i18n="hero.challengeLabel">Challenge</p>
|
||||
<p class="floating-label" data-i18n="hero.challengeLabel">Herausforderung</p>
|
||||
<p class="floating-value" data-i18n="hero.challengeValue">Zeit und Schritte messen</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -76,17 +62,20 @@
|
||||
|
||||
<section class="info-band" id="problem">
|
||||
<div class="info-band-inner">
|
||||
<p class="eyebrow" data-i18n="problem.eyebrow">Warum dieses Problem spannend ist</p>
|
||||
<h2 data-i18n="problem.title">Die Acht-Damen- und Acht-Tuerme-Probleme sind mehr als Schachrätsel.</h2>
|
||||
<p class="eyebrow" data-i18n="problem.eyebrow">Was ist das Acht-Damen-Problem?</p>
|
||||
<h2 data-i18n="problem.title">Eine klassische schachmathematische Aufgabe mit überraschend vielen
|
||||
Lösungen.</h2>
|
||||
<p data-i18n="problem.text">
|
||||
Beim Acht-Damen-Problem suchst du eine Anordnung von acht Damen auf einem Schachbrett, sodass sich
|
||||
keine zwei Damen schlagen. Beim Acht-Türme-Problem gilt dieselbe Idee für Türme, nur mit
|
||||
einfacheren Konfliktregeln. Beide Aufgaben zeigen, wie Mustererkennung, systematische Suche und
|
||||
Backtracking funktionieren.
|
||||
Das Acht-Damen-Problem ist eine schachmathematische Aufgabe: Acht Damen sollen auf einem
|
||||
Schachbrett so aufgestellt werden, dass keine zwei Damen dieselbe Reihe, Spalte oder Diagonale
|
||||
teilen. Das Problem lässt sich auf beliebige Brettgrössen verallgemeinern, indem n Damen auf einem
|
||||
n x n-Brett platziert werden.
|
||||
<br><br>
|
||||
Gerade beim Damenproblem wird schnell sichtbar, warum Strategie wichtig ist: Es gibt unzählige
|
||||
mögliche Platzierungen, aber nur sehr wenige wirklich gültige Lösungen. Genau das macht diese
|
||||
App spannend, weil man nicht nur das Ergebnis sieht, sondern auch den Weg dorthin.
|
||||
Für das klassische 8x8-Brett gibt es 92 Lösungen. Zählt man Stellungen, die nur durch Spiegelung
|
||||
oder Drehung entstehen, als gleich, bleiben 12 eindeutige Lösungen übrig.
|
||||
<br><br>
|
||||
<a href="https://de-academic.com/dic.nsf/dewiki/13248" target="_blank" rel="noopener noreferrer">Quelle:
|
||||
de-academic.com</a>
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
@@ -101,7 +90,7 @@
|
||||
<article class="feature-card">
|
||||
<span class="feature-icon">♟</span>
|
||||
<h3 data-i18n="features.card1Title">Interaktives Brett</h3>
|
||||
<p data-i18n="features.card1Text">Setze Damen oder Tuerme direkt auf dem Brett und prüfe, ob deine
|
||||
<p data-i18n="features.card1Text">Setze Damen oder Türme direkt auf dem Brett und prüfe, ob deine
|
||||
Anordnung konfliktfrei ist.</p>
|
||||
</article>
|
||||
|
||||
@@ -109,7 +98,8 @@
|
||||
<span class="feature-icon">⚙</span>
|
||||
<h3 data-i18n="features.card2Title">Solver mit Animation</h3>
|
||||
<p data-i18n="features.card2Text">Beobachte den Lösungsweg Schritt für Schritt und verstehe, wie
|
||||
Backtracking arbeitet.</p>
|
||||
Backtracking arbeitet.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article class="feature-card">
|
||||
@@ -121,8 +111,8 @@
|
||||
|
||||
<article class="feature-card">
|
||||
<span class="feature-icon">💾</span>
|
||||
<h3 data-i18n="features.card4Title">Save-Points</h3>
|
||||
<p data-i18n="features.card4Text">Speichere Zwischenstaende mit Notizen, Zeiten und Schrittzahl für
|
||||
<h3 data-i18n="features.card4Title">Speicherstände</h3>
|
||||
<p data-i18n="features.card4Text">Speichere Zwischenstände mit Notizen, Zeiten und Schrittzahl für
|
||||
späteres Weiterarbeiten.</p>
|
||||
</article>
|
||||
|
||||
@@ -145,8 +135,8 @@
|
||||
<article class="journey-step">
|
||||
<span class="step-number">01</span>
|
||||
<h3 data-i18n="journey.step1Title">Account erstellen</h3>
|
||||
<p data-i18n="journey.step1Text">Registriere dich und erhalte Zugang zu Dashboard, Save-Points und
|
||||
deinem Fortschritt.</p>
|
||||
<p data-i18n="journey.step1Text">Registriere dich und erhalte Zugang zum Dashboard, zu
|
||||
Speicherständen und zu deinem Fortschritt.</p>
|
||||
</article>
|
||||
|
||||
<article class="journey-step">
|
||||
@@ -160,8 +150,7 @@
|
||||
<span class="step-number">03</span>
|
||||
<h3 data-i18n="journey.step3Title">Lösen oder analysieren</h3>
|
||||
<p data-i18n="journey.step3Text">Teste eigene Ideen oder lass dir die Lösung visuell vom Solver
|
||||
lösen lassen, dabei wird 1:1
|
||||
gezeigt wie der Solver vorgeht.</p>
|
||||
zeigen, damit du den Lösungsweg besser nachvollziehen kannst.</p>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
@@ -177,9 +166,9 @@
|
||||
|
||||
<div class="hero-actions">
|
||||
<a class="hero-button primary" href="{{ url_for('login.register') }}"
|
||||
data-i18n="cta.primaryCta">Create Account</a>
|
||||
data-i18n="cta.primaryCta">Account erstellen</a>
|
||||
<a class="hero-button secondary" href="{{ url_for('login.login') }}"
|
||||
data-i18n="cta.secondaryCta">Login</a>
|
||||
data-i18n="cta.secondaryCta">Anmelden</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user