Source: shuffler.js

const {Anchor} = require('./anchor');

/**
 * @typedef {(pieces: import("./piece")[]) => import("./vector").Vector[]} Shuffler
 */

/**
 * @private
 */
function sampleIndex(list) {
  return Math.round(Math.random() * (list.length - 1));
}

/**
 * @param {number} maxX
 * @param {number} maxY
 *
 * @returns {Shuffler}
 */
function random(maxX, maxY) {
  return (pieces) => pieces.map(_it => Anchor.atRandom(maxX, maxY));
}

/**
 * @type {Shuffler}
 * */
const grid = (pieces) => {
  const destinations = pieces.map(it => it.centralAnchor.asVector());
  for (let i = 0; i < destinations.length; i++) {
    const j = sampleIndex(destinations);
    const temp = destinations[j];
    destinations[j] = destinations[i];
    destinations[i] = temp;
  }
  return destinations;
};

/**
 * @type {Shuffler}
 * */
const columns = (pieces) => {
  const destinations = pieces.map(it => it.centralAnchor.asVector());
  const columns = new Map();

  for (let destination of destinations) {
    if (!columns.get(destination.x)) {
      columns.set(destination.x, destinations.filter(it => it.x == destination.x));
    }
    const column = columns.get(destination.x);

    const j = sampleIndex(column);
    const temp = column[j].y
    column[j].y = destination.y;
    destination.y = temp;
  }
  return destinations;
};


/**
 * @type {Shuffler}
 * */
const line = (pieces) => {
  const destinations = pieces.map(it => it.centralAnchor.asVector());
  const columns = new Set(destinations.map(it => it.x));
  const maxX = Math.max(...columns);
  const minX = Math.min(...columns);
  const width = (maxX - minX) / (columns.size - 1);
  const pivot = minX + (width / 2);

  const lineLength = destinations.length * width;
  const linePivot = destinations.filter(it => it.x < pivot).length * width;

  const init = [];
  const tail = [];

  for (let i = 0; i < linePivot; i += width) {
    init.push(i);
  }

  for (let i = init[init.length - 1] + width; i < lineLength; i += width) {
    tail.push(i);
  }

  for (let destination of destinations) {
    const source = destination.x < pivot ? init : tail;
    const index = sampleIndex(source);

    destination.y = 0;
    destination.x = source[index];
    source.splice(index, 1);
  }
  return destinations;
};

/**
 * @param {number} padding
 * @param {number} width
 * @param {number} height
 * @returns {Shuffler}
 * */
function padder(padding, width, height) {
  return (pieces) => {
    const destinations = pieces.map(it => it.centralAnchor.asVector());
    let dx = 0;
    let dy = 0;
    for (let j = 0; j < height; j++) {
      for (let i = 0; i < width; i++) {
        const destination = destinations[i + width * j];
        destination.x += dx;
        destination.y += dy;

        dx += padding;
      }
      dx = 0;
      dy += padding;
    }
    return destinations;
  }
}

/**
 * @param {import('./vector').Vector} maxDistance
 * @returns {Shuffler}
 */
function noise(maxDistance) {
  return (pieces) => {
    return pieces.map(it =>
      Anchor
        .atRandom(2 * maxDistance.x, 2 * maxDistance.y)
        .translate(-maxDistance.x, -maxDistance.y)
        .translate(it.centralAnchor.x, it.centralAnchor.y)
        .asVector());
  }
}

/**
 * @type {Shuffler}
 * */
const noop = (pieces) => pieces.map(it => it.centralAnchor);

module.exports = {
  random,
  grid,
  columns,
  line,
  noop,
  padder,
  noise
}