Source: maze/obstaclePlacer.js

/**
 * This module takes a matrix that represents a maze and returns a list of
 * cells where obstacles will be placed.
 * @module
 */
define([
  'lodash',
  './matrixMaze',
], function(_, matrixMaze) {
  'use strict';

  var exports = {

    /**
     * See if a object is in a list of objects.
     *
     * @private
     * @param {Array.<Object>} listOfObjects A list of objects.
     * @param {Object} object The object that should be checked against the
     *   list.
     * @returns {boolean} True if `object` is equal to any of the objects in
     *   `listOfObjects`, false otherwise.
     */
    __objectIsInList: function(listOfObjects, object) {
      return _.some(listOfObjects, function(listObject) {
        return _.isEqual(listObject, object);
      });
    },

    /**
     * Check if any of the objects in `list2` are equal to the objects in
     * `list1`.
     *
     * @private
     * @param {Array.<Object>} list1 A list of objects.
     * @param {Array.<Object>} list2 A second list of objects.
     * @returns {boolean} True if any of the objects in `list1` is equal to any
     *   of the objects in `list2`.
     */
    __anyOfObjectAreInList: function(list1, list2) {
      return _.some(list2, function(cell) {
        return this.__objectIsInList(list1, cell);
      }.bind(this));
    },

    /**
     * Create and get coordinates for a number of obstacles. None of the
     * obstacles will be adjacent to each other.
     *
     * @param {Array.<Array.<Object>>} maze A matrix that represents a maze.
     * @param {boolean} maze.wall If it's a wall or floor.
     * @param {Array.<Object>} ignoreCells A list of cells to ignore even if
     * they are floor cells.
     * @param {number} ignoreCells.x
     * @param {number} ignoreCells.y
     * @param {number} numberOfObstacles The maximum number of obstacles that
     *   will be placed into the maze.
     * @returns {Array.<Object>} A list of cells where the obstacles should be
     *   placed. Each list element is an object with the attributes `x` and
     *   `y`.
     */
    place: function(maze, ignoreCells, numberOfObstacles) {
      var potentialCells = [];
      // Iterate through all the maze's cells and add store which ones are
      // floors and not in the `ignoreCells` list.
      _.forEach(maze, function(row, rowIndex) {
        _.forEach(maze, function(cell, cellIndex) {
          if (
            // The cell isn't a floor
            !maze[rowIndex][cellIndex].wall &&
            // And the cell isn't in the ignore list
            !this.__objectIsInList(ignoreCells, {y: rowIndex, x: cellIndex})
          ) {
            potentialCells.push({y: rowIndex, x: cellIndex});
          }
        }.bind(this));
      }.bind(this));

      // Shuffle the list so they are taken in random order
      potentialCells = _.shuffle(potentialCells);

      // Where the obstacles will be placed
      var obstacles = [];
      // Loop while there are potential cells left and it hasn't exceeded the
      // `numberOfObstacles` cap.
      for (
        var i = 0;
        obstacles.length < numberOfObstacles && i < potentialCells.length;
        ++i
      ) {
        var cell = potentialCells[i];
        // Get all neighbor cells
        var neighborCells = matrixMaze.getCellNeighbors(maze, cell.y, cell.x);
        // Only add the cell as an obstacle if none of its neighbors are
        // already in the obstacles list
        if (!this.__anyOfObjectAreInList(obstacles, neighborCells)) {
          obstacles.push({y: cell.y, x: cell.x});
        }
      }
      return obstacles;
    },
  };

  return exports;
});