Source: Frontend/game/browser/game-map.mjs

/* eslint-disable complexity,no-mixed-operators,consistent-return */
/* global PIXI */
/** @module browser/GameMap */
import GameMapCommon from "../common/game-map/game-map.mjs";

const DEFAULT_RENDERER = "smooth-renderer";

/**
 * A renderer for the entire game map
 * @typedef SpriteReturn
 * @prop {PIXI.Container} sprite The sprite representing the game map
 * @prop {Function} update GameMap.updateSprite but without the container parameter
 */
class GameMapRenderer {
  constructor(map) {
    this.map = map;
    this.sprite = new PIXI.Container();
  }

  /**
   * Render a subset of the map inside rectangle specified by the coordiates
   * @param {PIXI.Container} container The container returned by create sprite
   * @param {number} xMin x coordinate for the top left corner
   * @param {number} yMin y coordinate for the top left corner
   * @param {number} xMax x coordinate for the bottom right corner
   * @param {number} yMax y coordinate for the bottom right corner
   */
  update(xMin, yMin, xMax, yMax) {
    /* eslint-disable no-param-reassign */
    xMin = Math.max(0, xMin);
    yMin = Math.max(0, yMin);
    /* eslint-enable no-param-reassign */

    if(this.__prevXMin === xMin && this.__prevXMax === xMax && 
      this.__prevYMin === yMin && this.__prevYMax === yMax) {
      return;
    }

    this.__prevXMin = xMin;
    this.__prevXMax = xMax;
    this.__prevYMin = yMin;
    this.__prevYMax = yMax;

    while(this.sprite.children.length) {
      this.sprite.removeChild(this.sprite.children[0]);
    }

    // check that a rect is inside the box even partially
    let inBounds = (rect) => {
      return ((xMin <= rect.x && rect.x <= xMax) ||
        (xMin <= rect.x + rect.width && rect.x + rect.width <= xMax) ||
        (xMin > rect.x && rect.x + rect.width > xMax)) &&
        ((yMin <= rect.y && rect.y <= yMax) ||
        (yMin <= rect.y + rect.height && rect.y + rect.height <= yMax) ||
        (yMin > rect.y && rect.y + rect.height > yMax));
    };

    // process a single room/corridor
    let process = (rect) => {
      let renderer = GameMap._renderers.get(rect._rendererName);

      if(!renderer) {
        renderer = GameMap._renderers.get(DEFAULT_RENDERER);
      }

      // get corrds relative to the screen
      let x = rect.x - xMin;
      let y = rect.y - yMin;
      let xEnd = x + rect.width;
      let yEnd = y + rect.height;
      let width = xEnd - x;
      let height = yEnd - y;
      // get corrds relative to the rect
      let relativeX = Math.max(x - rect.x, 0);
      let relativeY = Math.max(y - rect.y, 0);
      let relativeWidth = width - relativeX - Math.max(xEnd - xMax, 0);
      let relativeHeight = height - relativeY - Math.max(yEnd - yMax, 0);

      let sprites = renderer.render({
        x: relativeX,
        y: relativeY,
        width: relativeWidth,
        height: relativeHeight,
        rect,
        xMin,
        xMax,
        yMin,
        yMax,
        map: this.map
      });

      // positon and size the sprites that were given
      sprites.position.set(x, y);
      sprites.width = Math.min(xMax, xEnd) - x;
      sprites.height = Math.min(yMax, yEnd) - y;
      this.sprite.addChild(sprites);
    };

    // process all of the rooms and corridors
    for(let room of this.map.rooms) {
      if(inBounds(room)) {
        process(room);
      }
      
      if(room.left && inBounds(room.left)) {
        process(room.left);
      }

      if(room.above && inBounds(room.above)) {
        process(room.above);
      }
    }
  }
}

export default class GameMap extends GameMapCommon {
  /**
   * Geneate a game map
   * @returns {GameMap}
   */
  static generate(params) {
    return GameMapCommon.generate(new GameMap(), params);
  }
  
  /**
   * Load the game map in the browser
   * @param {Floor} floor The floor to load the map for
   */
  static load(floor) {
    return fetch(`/game/map/${floor.id}`)
      .then((res) => {
        return res.json();
      })
      .then((json) => {
        floor.map = GameMap.parse(json, new GameMap());
      });
  }

  /**
   * Create a new renderer for this game map
   */
  createRenderer() {
    return new GameMapRenderer(this);
  }
}