(function () { "use strict"; // ===== State ===== let boardWidth = 5; let boardHeight = 5; // colConstraints[c] = number of cells that should be filled in column c let colConstraints = []; // rowConstraints[r] = number of cells that should be filled in row r let rowConstraints = []; // blocked[r][c] = true if that cell is blocked let blocked = []; // pieces: array of 3x3 boolean grids e.g. [[false,true,false],[true,true,true],[false,false,false]] let pieces = []; // piece designer state (3x3) let pieceDesign = [ [false, false, false], [false, false, false], [false, false, false] ]; // ===== DOM refs ===== const boardWidthInput = document.getElementById("boardWidth"); const boardHeightInput = document.getElementById("boardHeight"); const generateBtn = document.getElementById("generateBtn"); const boardArea = document.getElementById("boardArea"); const pieceDesignerGrid = document.getElementById("pieceDesignerGrid"); const addPieceBtn = document.getElementById("addPieceBtn"); const pieceInventory = document.getElementById("pieceInventory"); const exportBtn = document.getElementById("exportBtn"); const exportOutput = document.getElementById("exportOutput"); // ===== Board generation ===== function initBoard() { boardWidth = parseInt(boardWidthInput.value) || 5; boardHeight = parseInt(boardHeightInput.value) || 5; colConstraints = new Array(boardWidth).fill(0); rowConstraints = new Array(boardHeight).fill(0); blocked = []; for (let r = 0; r < boardHeight; r++) { blocked.push(new Array(boardWidth).fill(false)); } renderBoard(); } function renderBoard() { // Build table: top-left empty | col spinboxes // row spinbox | cells let html = ''; // Header row: empty corner + column spinboxes html += ""; html += ''; // top-left corner for (let c = 0; c < boardWidth; c++) { html += '"; } html += ""; // Board rows for (let r = 0; r < boardHeight; r++) { html += ""; // Row spinbox html += '"; for (let c = 0; c < boardWidth; c++) { const blockedClass = blocked[r][c] ? " blocked" : ""; html += ``; } html += ""; } html += "
'; html += ``; html += "
'; html += ``; html += "
"; boardArea.innerHTML = html; // Attach events boardArea.querySelectorAll(".board-cell").forEach(cell => { cell.addEventListener("click", function () { const r = parseInt(this.dataset.row); const c = parseInt(this.dataset.col); blocked[r][c] = !blocked[r][c]; this.classList.toggle("blocked"); }); }); boardArea.querySelectorAll(".col-constraint").forEach(input => { input.addEventListener("change", function () { colConstraints[parseInt(this.dataset.col)] = parseInt(this.value) || 0; }); }); boardArea.querySelectorAll(".row-constraint").forEach(input => { input.addEventListener("change", function () { rowConstraints[parseInt(this.dataset.row)] = parseInt(this.value) || 0; }); }); } // ===== Piece designer ===== function renderPieceDesigner() { pieceDesignerGrid.innerHTML = ""; for (let r = 0; r < 3; r++) { for (let c = 0; c < 3; c++) { const cell = document.createElement("div"); cell.className = "piece-designer-cell" + (pieceDesign[r][c] ? " active" : ""); cell.dataset.row = r; cell.dataset.col = c; cell.addEventListener("click", function () { const pr = parseInt(this.dataset.row); const pc = parseInt(this.dataset.col); pieceDesign[pr][pc] = !pieceDesign[pr][pc]; this.classList.toggle("active"); }); pieceDesignerGrid.appendChild(cell); } } } function addPiece() { // Check that at least one cell is active const hasCell = pieceDesign.some(row => row.some(v => v)); if (!hasCell) return; // Deep-copy the design const copy = pieceDesign.map(row => row.slice()); pieces.push(copy); // Reset designer pieceDesign = [ [false, false, false], [false, false, false], [false, false, false] ]; renderPieceDesigner(); renderInventory(); } function renderInventory() { pieceInventory.innerHTML = ""; pieces.forEach((piece, index) => { const wrapper = document.createElement("div"); wrapper.className = "inventory-piece"; for (let r = 0; r < 3; r++) { for (let c = 0; c < 3; c++) { const cell = document.createElement("div"); cell.className = "inventory-piece-cell" + (piece[r][c] ? " active" : ""); wrapper.appendChild(cell); } } // Delete button const delBtn = document.createElement("button"); delBtn.className = "delete-piece-btn"; delBtn.textContent = "×"; delBtn.title = "Delete piece"; delBtn.addEventListener("click", function (e) { e.stopPropagation(); pieces.splice(index, 1); renderInventory(); }); wrapper.appendChild(delBtn); pieceInventory.appendChild(wrapper); }); } // ===== Export ===== function exportPuzzle() { const data = { width: boardWidth, height: boardHeight, colConstraints: colConstraints, rowConstraints: rowConstraints, blocked: blocked, pieces: pieces }; const json = JSON.stringify(data); const encoded = btoa(json); exportOutput.style.display = "block"; exportOutput.value = encoded; exportOutput.select(); } // ===== Event bindings ===== generateBtn.addEventListener("click", initBoard); addPieceBtn.addEventListener("click", addPiece); exportBtn.addEventListener("click", exportPuzzle); // Also auto-update board on input change boardWidthInput.addEventListener("change", initBoard); boardHeightInput.addEventListener("change", initBoard); // ===== Init ===== initBoard(); renderPieceDesigner(); })();