Source: scripts/helpers.js

// helpers.js

/**
 * Stores all interval IDs created via setStoppableInterval.
 * Used to manage and clear all game intervals when needed.
 * @type {number[]}
 */
let intervalIds = [];

/**
 * Stores all requestAnimationFrame IDs created via setStoppableRAF.
 * Used to manage and cancel all animation frames when needed.
 * @type {number[]}
 */
let rAFIds = [];

/**
 * Creates a setInterval and registers its ID for global tracking.
 * This allows all such intervals to be stopped later via stopAllLoops().
 *
 * @param {Function} fn - The function to execute repeatedly.
 * @param {number} time - The interval delay in milliseconds.
 */
function setStoppableInterval(fn, time) {
  let id = setInterval(fn, time);
  intervalIds.push(id);
}

/**
 * Creates a requestAnimationFrame call and registers its ID for global tracking.
 * This allows all animation frames to be cancelled when needed.
 *
 * @param {Function} fn - The function to execute on the next animation frame.
 * @returns {void}
 */
function setStoppableRAF(fn) {
  let id = requestAnimationFrame(fn);
  rAFIds.push(id);
}

/**
 * Removes an object from an array, optionally after a delay.
 * If a delay is specified, removal is deferred using setTimeout.
 *
 * @param {Object} object - The object to remove.
 * @param {Array} array - The array from which to remove the object.
 * @param {number} [delay=0] - Optional delay in milliseconds before removal.
 */
function despawnObject(object, array, delay = 0) {
  if (delay > 0) {
    setTimeout(() => {
      removeFromArray(object, array);
    }, delay);
  } else {
    removeFromArray(object, array);
  }
}

/**
 * Removes the specified object from the given array.
 * If the object is not found, nothing happens.
 *
 * @param {Object} object - The object to remove.
 * @param {Array} array - The array to modify.
 */
function removeFromArray(object, array) {
  let index = array.indexOf(object);
  array.splice(index, 1);
}

/**
 * Shorthand for document.getElementById.
 *
 * @param {string} id - The ID of the HTML element to retrieve.
 * @returns {HTMLElement} The matching element.
 */
function getElementByIdHelper(id) {
  let element = document.getElementById(id);
  return element;
}

/**
 * Removes focus from all elements matching the given selector.
 *
 * @param {string} selector - A CSS selector (e.g. '.btn')
 */
function blurButton(selector) {
  document.querySelectorAll(selector).forEach((el) => el.blur());
}

/**
 * Adds the 'd-none' class to the element with the given ID using getElementByIdHelper.
 *
 * @param {string} id - The ID of the HTML element to hide.
 */
function hideElementById(id) {
  let element = getElementByIdHelper(id);
  if (element) {
    element.classList.add("d-none");
  }
}

/**
 * Removes the 'd-none' class from the element with the given ID using getElementByIdHelper.
 *
 * @param {string} id - The ID of the HTML element to show.
 */
function showElementById(id) {
  let element = getElementByIdHelper(id);
  if (element) {
    element.classList.remove("d-none");
  }
}

/**
 * Saves the current sound settings to local storage.
 * Stores the mute state and individual sound volumes as serialized JSON.
 */
function saveToLocalStorage() {
  localStorage.setItem("muteState", JSON.stringify(SoundManager.isMuted));
  localStorage.setItem("volumeState", JSON.stringify(Array.from(SoundManager.volumes.entries())));
}

/**
 * Loads sound settings from local storage and restores them.
 * Applies the mute state and reconstructs the volume map for all sounds.
 */
function loadFromLocalStorage() {
  SoundManager.isMuted = JSON.parse(localStorage.getItem("muteState"));
  const rawVolumes = JSON.parse(localStorage.getItem("volumeState"));
  SoundManager.volumes = new Map(rawVolumes);
}