core/terrain-utils.js

import { Registry } from "./registry.js";
import { ON } from "./state.js";

/**
 * Utility helpers for terrain manipulation.
 * Mirrors TerrainUtils.java.
 */
export const TerrainUtils = {
  /**
   * Remove a decorator from every cell on the board where it appears,
   * replacing it with the decorator's proxied (wrapped) terrain.
   *
   * Used by TriggerOnce and similar one-shot decorators.
   *
   * @param {GameEvent} event
   * @param {Terrain} terrain  the decorator instance to remove
   */
  removeDecorator(event, terrain) {
    event.board.visit((cell) => {
      if (cell.terrain === terrain) {
        // terrain is a Decorator — replace it with the proxied terrain
        cell.setTerrain(terrain.getProxiedTerrain());
      }
      return false; // keep visiting
    });
  },

  /**
   * Toggle the state of the terrain in `cell` (ON→OFF or OFF→ON).
   * Does nothing if an agent occupies the cell.
   *
   * @param {Cell} cell
   * @param {Terrain} terrain
   * @param {State} state  current state of the terrain
   */
  toggleCellState(cell, terrain, state) {
    if (cell.agent == null) {
      const other = this.getTerrainOtherState(terrain, state);
      cell.setTerrain(other);
    }
  },

  /**
   * Return the same terrain type but with its state toggled (ON↔OFF).
   * Works by manipulating the Registry serialization key: replaces the
   * word "on" with "off" (or vice-versa) then looks up the new key.
   *
   * @param {Terrain} terrain
   * @param {State} state  current state
   * @returns {Terrain}
   */
  getTerrainOtherState(terrain, state) {
    let key = Registry.serialize(terrain);
    if (state.isOn()) {
      key = key.replace(/\bon\b/g, "off");
    } else {
      key = key.replace(/\boff\b/g, "on");
    }
    return Registry.get(key);
  },

  /**
   * Return the cell on the opposite side of the board from `cell`
   * when traveling in `direction`. Used by rolling-boulder wrap-around.
   *
   * @param {Cell} cell
   * @param {Direction} direction
   * @returns {Cell|null}
   */
  getCellOnOppositeSide(cell, direction) {
    const { ROWS, COLUMNS } = /** @type {any} */ (cell.board);
    if (cell.y === 0 || cell.y === ROWS - 1) {
      if (direction.isNortherly()) {
        return cell.board.getCellAt(cell.x, ROWS - 1);
      } else if (direction.isSoutherly()) {
        return cell.board.getCellAt(cell.x, 0);
      } else if (direction.isEasterly()) {
        return cell.board.getCellAt(0, cell.y);
      } else if (direction.isWesterly()) {
        return cell.board.getCellAt(COLUMNS - 1, cell.y);
      }
    } else {
      if (direction.isEasterly()) {
        return cell.board.getCellAt(0, cell.y);
      } else if (direction.isWesterly()) {
        return cell.board.getCellAt(COLUMNS - 1, cell.y);
      } else if (direction.isNortherly()) {
        return cell.board.getCellAt(cell.x, ROWS - 1);
      } else if (direction.isSoutherly()) {
        return cell.board.getCellAt(cell.x, 0);
      }
    }
    return null;
  },
};