Tetris Shape Maker

by Ben Gillbanks

HTML

<h1>Tetris style Block Editor</h1>
	<p>Select cells to design a shape. Then click "Generate Rotations".</p>
	<table id="grid"></table>
	<button id="generate">Generate Shape Rotations</button>
	<pre id="output"></pre>

CSS

table {
			border-collapse: collapse;
			margin: 20px 0;
		}
		td {
			width: 30px;
			height: 30px;
			text-align: center;
			border: 1px solid #ccc;
		}

JavaScript

// Create a 4x4 grid of checkboxes.
const grid = document.getElementById('grid');
const gridSize = 4;
for (let y = 0; y < gridSize; y++) {
	const row = document.createElement('tr');
	for (let x = 0; x < gridSize; x++) {
		const cell = document.createElement('td');
		const checkbox = document.createElement('input');
		checkbox.type = 'checkbox';
		checkbox.id = `cell-${x}-${y}`;
		cell.appendChild(checkbox);
		row.appendChild(cell);
	}
	grid.appendChild(row);
}

// Normalize the shape so its top-left coordinate is [0,0].
function normalizeShape(shape) {
	const xs = shape.map(c => c[0]);
	const ys = shape.map(c => c[1]);
	const minX = Math.min(...xs);
	const minY = Math.min(...ys);
	return shape.map(([x, y]) => [x - minX, y - minY]);
}

// Rotate a shape 90° counterclockwise.
function rotateShape(shape) {
	const norm = normalizeShape(shape);
	const ys = norm.map(c => c[1]);
	const height = Math.max(...ys) + 1;
	// Rotation formula: [x, y] -> [height - 1 - y, x].
	const rotated = norm.map(([x, y]) => [height - 1 - y, x]);
	return normalizeShape(rotated);
}

// Check if two shapes (arrays of coordinate pairs) are equal.
function arraysEqual(a, b) {
	if (a.length !== b.length) return false;
	for (let i = 0; i < a.length; i++) {
		if (a[i][0] !== b[i][0] || a[i][1] !== b[i][1]) return false;
	}
	return true;
}

// See if a shape is already in a list.
function shapeInList(shape, list) {
	return list.some(item => arraysEqual(item, shape));
}

// Generate unique rotations (up to 4) until they repeat.
function generateRotations(shape) {
	const rotations = [];
	let current = normalizeShape(shape);
	rotations.push(current);
	for (let i = 0; i < 3; i++) {
		current = rotateShape(current);
		if (shapeInList(current, rotations)) {
			break;
		} else {
			rotations.push(current);
		}
	}
	return rotations;
}

// Read the grid and return the selected cell coordinates.
function getShapeFromGrid() {
	const shape = [];
	for (let y = 0; y < gridSize; y++) {
		for (let x = 0; x <...