A small grid puzzle is a useful exercise in defensive programming. A move should be checked against the old board before any cells change. Otherwise a rejected move can leave a half-written shape behind.
This example uses a rectangular array of booleans. true means occupied. A shape is a list of row and column offsets from its top-left anchor. The code is a teaching example, not a description of any particular game's implementation.
function canPlace(board, shape, row, col) {
if (!board.length || !board[0].length || !shape.length) return false;
const height = board.length;
const width = board[0].length;
if (!board.every(line => line.length === width)) return false;
const seen = new Set();
for (const [dr, dc] of shape) {
if (!Number.isInteger(dr) || !Number.isInteger(dc)) return false;
const r = row + dr;
const c = col + dc;
if (!Number.isInteger(r) || !Number.isInteger(c)) return false;
if (r < 0 || r >= height || c < 0 || c >= width) return false;
const key = `${r},${c}`;
if (seen.has(key) || board[r][c]) return false;
seen.add(key);
}
return true;
}The function is deliberately read-only. It rejects out-of-bounds cells, collisions, duplicate offsets and malformed coordinates. Duplicate offsets matter because an invalid shape should not quietly turn into a smaller one.
Keep validation separate from commitment
After a successful check, copy the board, write every cell of the shape to the copy, and only then replace the current board. If the interface animates the move, commit the logical state once. Repeated animation frames should not repeatedly apply it.
For a two-by-two square, use [[0,0],[0,1],[1,0],[1,1]]. On an empty eight-by-eight board, the anchor (6,6) fits and (7,6) does not. A collision in even one of the four cells rejects the whole move.
Clear lines as a separate operation
If your rules clear completed rows and columns together, discover all completed lines from the same post-placement board. Collect their cell coordinates in a set, then clear the union. A row and column sharing one cell should clear that intersection once. Scanning, clearing, then scanning again can produce a different result from simultaneous clearing.
Test boundaries before adding polish
Useful cases include an anchor in each corner, a single occupied cell under a large shape, a shape extending beyond the right edge, and an unsuccessful move that leaves the original board unchanged. Check both the returned result and the unchanged input. A pretty drag preview cannot compensate for a state mutation bug.
Reuse the same validator for the preview
A drag preview and a committed move should answer the same placement question. Convert the pointer position to a single grid anchor, pass that anchor to the validator, and use the result to style the preview. When the player releases the pointer, validate again against the current board. This second check matters if another action can change the board while the pointer is down.
Keep pixels out of the collision function. The interface can calculate cell coordinates from its layout; the board rules should receive integer coordinates only. This makes the rule code usable from a keyboard interface or a test runner as well as from a mouse drag.
For each test, create a fresh board rather than reusing the board changed by the previous case. A failing test should identify a rule, not an accidental dependency on test order. For an accepted move, verify that the number of newly occupied cells equals the number of distinct offsets before applying any line-clearing rule.
For a browser-based practice reference, Block Blast on blockblast.co lets readers explore block placement, and its solver page provides another place to inspect puzzle positions. The JavaScript above is independent educational code; it does not claim to reproduce that site's internal logic. Disclosure: these links point to the site this article's publisher is promoting; it is an unofficial project and is not Hungry Studio.

Responses (0)
Join the conversation
Sign in to share your thoughts and talk with the author.
Sign in to respondNo responses yet. Be the first to share your thoughts!