sparkletown/sparkle

View on GitHub
src/components/templates/AnimateMap/bridges/DataProvider/Structures/RoomsModel.ts

Summary

Maintainability
A
3 hrs
Test Coverage
import { Box, Point as QPoint, QuadTree } from "js-quadtree";
import { intersection } from "lodash";

import { RoomInfo } from "../../../vendors/playerio/PlayerIO";
import EventProvider, { EventType } from "../../EventProvider/EventProvider";
import {
  RoomMath,
  Tuple,
} from "../Contructor/PlayerIO/RoomOperator/RoomLogic/RoomMath";
import { RoomTypes } from "../Contructor/PlayerIO/types";

export const ROOM_PREFIX = "Z_";
export const MIRROR_ROOM_PREFIX = "mZ_"; //Note: don't make this prefix a substring ROOM_PREFIX (or rewrite getRoomNumberById)

export const initialRoomData = {};
export type RoomDataType = typeof initialRoomData;
export type RoomInfoType = RoomInfo<RoomDataType, RoomTypes>;

export interface RoomItem extends RoomInfoType {
  roomNumber: number;
  depth: number;
  bounds: Tuple<[number, number], 4>;
}

export interface RoomPointNode {
  x: number;
  y: number;
  data: string[]; //rooms ids
}

/**
 * A complex data structure using an array for the basic representation of rooms.
 * And also using quadtree, for a quick search for the crossing of main room.
 */
export class RoomsModel {
  private _list: RoomItem[] = [];
  private _idList: string[] = [];
  private _tree: QuadTree;
  private _treeItemList: RoomPointNode[] = [];

  get size() {
    return this._list.length;
  }

  constructor(public worldWidth: number, public worldHeight: number) {
    this._tree = new QuadTree(new Box(0, 0, worldWidth, worldHeight), {
      maximumDepth: 20,
      capacity: 9,
      removeEmptyNodes: true,
    });
  }

  updateList(listRooms: RoomInfoType[]) {
    const newList = normilizeListRoom(listRooms);
    const newIdList = newList.map((item) => item.id);

    // const notChangedRooms = newIdList.filter(item => this._idList.includes(item));
    const addedRooms = newIdList.filter((item) => !this._idList.includes(item));
    // const removedRooms = this._idList.filter(item => !newIdList.includes(item));

    // if (removedRooms.length > 0) {
    //
    // }
    //
    // if (removedRooms.length > 0 && notChangedRooms.length > 0) {
    //
    // }

    if (addedRooms.length > 0) {
      this._addRoomsToTree(addedRooms);
    }

    this._list = newList;
  }

  divideRoomNode(room: RoomItem) {
    const roomsNumbers = RoomMath.getDividedParts(room.roomNumber);
    return roomsNumbers.map((roomNumber) => this.createRoomNode(roomNumber));
  }

  createRoomNode(roomNumber: number) {
    const roomId = getRoomIdByRoomNumber(roomNumber);
    const { depth, bounds } = RoomMath.getFramePoint(roomNumber);
    console.log(bounds);
    const newRoom: RoomItem = {
      id: roomId,
      roomNumber: roomNumber,
      depth: depth,
      bounds: bounds,
      roomType: RoomTypes.Zone,
      roomData: initialRoomData,
      onlineUsers: 0,
    };
    this._addRoomsToTree([roomId]);
    this._list.push(newRoom);
    this._idList.push(roomId);
    return newRoom;
  }

  private _addRoomsToTree(roomIds: string[]) {
    const treeItems = this._treeItemList;
    const newPoints: QPoint[] = [];
    roomIds.forEach((roomId) => {
      const { bounds: points } = RoomMath.getFramePoint(
        getRoomNumberById(roomId)
      );

      points.forEach((point) => {
        const x = point[0];
        const y = point[1];
        const existingPoint = treeItems.find(
          (item) => item.x === x && item.y === y
        );
        if (existingPoint) existingPoint.data.push(roomId);
        else {
          const p = { x: x, y: y, data: [roomId] };
          newPoints.push(p);
          treeItems.push(p);
        }
      });

      EventProvider.emit(EventType.ON_ROOMS_CHANGED, this._treeItemList);
    });
    this._tree.insert(newPoints);
  }

  public getRoomByPoint(point: { x: number; y: number }) {
    const roomsResult = this._list.filter((room) =>
      RoomMath.isPointInBounds(point, room.bounds)
    );

    if (roomsResult.length === 1) return roomsResult[0];

    if (roomsResult.length === 0) {
      const allRooms = this._list.map((room) => room.roomNumber);
      //todo: relocate to math class
      let offset = 1;
      let tempId = 0;
      for (let n = 1; n < 9; n++) {
        //todo: max_depth

        const maxRoomId = Math.pow(9, n);
        for (let i = 0; i < maxRoomId; i++) {
          if (
            RoomMath.isPointInBounds(
              point,
              RoomMath.getFramePoint(i + offset).bounds
            )
          ) {
            tempId = i + offset;
            break;
          }
        }

        //find neighbours
        const neighbours = [];
        const remainder = tempId % 9;
        const startNumber = tempId - remainder;
        for (let i = 0; i < 9; i++) neighbours.push(startNumber + i);

        if (intersection(allRooms, neighbours).length)
          return this.createRoomNode(tempId);

        offset += maxRoomId;
      }
      //create new one
      // const roomId = getRoomIdByRoomNumber(roomNumber);
      // const newRoom : RoomItem = {
      //   id: roomId,
      //   roomNumber: roomNumber,
      //   roomType: RoomTypes.Zone,
      //   roomData: initialRoomData,
      //   onlineUsers: 1,
      // }
      // this._addRoomsToTree([roomId]);
      // this._list.push(newRoom);
      // this._idList.push(roomId);
      // return newRoom;
    } else {
      console.log("WTF");
      console.log(roomsResult);
    }
  }
}

//utils methods
const normilizeListRoom = (listRooms: RoomInfoType[]) => {
  return listRooms.map((item) => {
    const roomNumber = getRoomNumberById(item.id);
    return {
      ...item,
      roomNumber: roomNumber,
      ...RoomMath.getFramePoint(roomNumber),
    };
  }) as RoomItem[];
};

const getRoomIdByRoomNumber = (roomNumber: number) => {
  return ROOM_PREFIX + roomNumber;
};

const getRoomNumberById = (id: string) => {
  return parseInt(
    id.includes(MIRROR_ROOM_PREFIX)
      ? id.slice(MIRROR_ROOM_PREFIX.length)
      : id.slice(ROOM_PREFIX.length)
  );
};

// const isMirror = (name: string) => {
//   return name[0] === "m";
// };