Files
endfieldpuzzle/designer.js
2026-03-13 17:58:49 +01:00

201 lines
7.1 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(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();
})();