fifteen
values
A visualization of the famous Fifteen puzzle, where the hash of the input is used to move the tiles around.
🔗 Layout
A lot of these pieces work on canvases of all sizes and shapes (as long as they’re rectangular). Many pieces can reasonably centered in a “square” based on the minimum of the width and height of the canvas, but for this piece I wanted to intelligently lay out the individual bits.
Given a width, height, and number of things, attempt to product an aesthetically-pleasing layout as defined by:
-
The number of rows
-
The number of columns
-
The size of each item
-
The space between each item
function computeIdealLayout(w, h, grids) {
const idealNegativeSpace = (w * h) / 2
let bestLayout = {
negativeSpace: Infinity,
}
for (
let columns = 1;
columns < grids.length;
columns++
) {
for (
let puzzleSize = 8;
puzzleSize < 200;
puzzleSize += 4
) {
for (
let padding = puzzleSize / 4;
padding < puzzleSize;
padding += 1
) {
let rows = Math.ceil(
grids.length / columns
)
let layoutWidth =
columns * puzzleSize +
(columns + 1) * padding
let layoutHeight =
rows * puzzleSize +
(rows + 1) * padding
// Doesn't fit, continue
if (
layoutWidth > w ||
layoutHeight > h
) {
continue
}
const negativeSpace =
// Size of the canvas
w * h -
// Subtract the area of the puzzles
puzzleSize *
puzzleSize *
columns *
rows
if (
Math.abs(
idealNegativeSpace -
negativeSpace
) <
Math.abs(
idealNegativeSpace -
bestLayout.negativeSpace
)
) {
bestLayout = {
negativeSpace,
columns,
rows,
layoutWidth,
layoutHeight,
padding,
puzzleSize,
}
}
}
}
}
return bestLayout
}
My particular approach has two assumptions coded in. First, an individual “thing” has a minimum size of 8, a maximum of 200, and must be a multiple of 2. Second, the padding should fall between 1/4th of the item size, to the item size.
After that, we simply loop a bunch of times over the different parameters to generate a triple (rows, padding, puzzleSize). For each of these we compute (1) if the pieces even fit on the canvas with these parameters and (2) how much whitespace will be left after everything is laid out.
We then pick the candidate whose whitespace is closest to 1/2 of the entire area of the canvas. This can be tweaked as we see fit (see
idealNegativeSpace
).
I’m shocked it works and runs quickly! There are many unnecessary loops and there is likely a better way to search for a good layout - but it’s fast enough for typing so it’s fast enough for me.