(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 = '
";
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();
})();