initial commit
This commit is contained in:
200
designer.js
Normal file
200
designer.js
Normal file
@@ -0,0 +1,200 @@
|
||||
(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 = '<table class="board-table">';
|
||||
|
||||
// Header row: empty corner + column spinboxes
|
||||
html += "<tr>";
|
||||
html += '<td></td>'; // top-left corner
|
||||
for (let c = 0; c < boardWidth; c++) {
|
||||
html += '<td class="constraint-label">';
|
||||
html += `<input type="number" min="0" class="col-constraint" data-col="${c}" value="${colConstraints[c]}">`;
|
||||
html += "</td>";
|
||||
}
|
||||
html += "</tr>";
|
||||
|
||||
// Board rows
|
||||
for (let r = 0; r < boardHeight; r++) {
|
||||
html += "<tr>";
|
||||
// Row spinbox
|
||||
html += '<td class="constraint-label">';
|
||||
html += `<input type="number" min="0" class="row-constraint" data-row="${r}" value="${rowConstraints[r]}">`;
|
||||
html += "</td>";
|
||||
for (let c = 0; c < boardWidth; c++) {
|
||||
const blockedClass = blocked[r][c] ? " blocked" : "";
|
||||
html += `<td class="board-cell${blockedClass}" data-row="${r}" data-col="${c}"></td>`;
|
||||
}
|
||||
html += "</tr>";
|
||||
}
|
||||
|
||||
html += "</table>";
|
||||
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();
|
||||
})();
|
||||
Reference in New Issue
Block a user