Home
🔢

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:
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.