yasshi2525/RushHour

View on GitHub
client/src/models/rail.ts

Summary

Maintainability
F
4 days
Test Coverage
import { Monitorable, MonitorContainer } from "interfaces/monitor";
import {
  AnimatedSpriteProperty,
  cursorOpts,
  AnimatedSpriteContainerProperty
} from "interfaces/pixi";
import { ResolveError, Point, MenuStatus } from "interfaces/gamemap";
import { AnimatedSpriteModel, AnimatedSpriteContainer } from "./sprite";
import { PointModel } from "./point";

const graphicsOpts = {
  width: 4,
  color: 0x9e9e9e,
  slide: 12
};

const rnDefaultValues: {
  pid: number;
  cid: number;
  mul: number;
} = {
  pid: 0,
  cid: 0,
  mul: 1
};

export class RailNode extends AnimatedSpriteModel implements Monitorable {
  out: { [index: string]: RailEdge };
  in: { [index: string]: RailEdge };

  constructor(options: AnimatedSpriteProperty) {
    super(options);
    this.out = {};
    this.in = {};
  }

  setupDefaultValues() {
    super.setupDefaultValues();
    this.addDefaultValues(rnDefaultValues);
  }

  setupUpdateCallback() {
    super.setupUpdateCallback();
    this.addUpdateCallback("visible", () => {
      Object.keys(this.out).forEach(eid => this.out[eid].updateVisible());
      Object.keys(this.in).forEach(eid => this.in[eid].updateVisible());
    });
  }

  getResourceName() {
    return "rail_nodes";
  }

  setupAfterCallback() {
    super.setupAfterCallback();
    this.addAfterCallback(() => {
      Object.keys(this.out).forEach(eid =>
        this.out[eid].merge("from", undefined)
      );
      Object.keys(this.in).forEach(eid => this.in[eid].merge("to", undefined));
    });
  }

  resolve(error: ResolveError) {
    error = super.resolve(error);
    let owner = this.resolveOwner(this.props.oid);
    if (owner !== undefined) {
      this.merge("tint", owner.get("color"));
    }
    let hasUnresolvedOwner = error.hasUnresolvedOwner || owner === undefined;
    error.hasUnresolvedOwner = hasUnresolvedOwner;
    return error;
  }
}

export class RailNodeContainer
  extends AnimatedSpriteContainer<RailNode, AnimatedSpriteProperty>
  implements MonitorContainer {
  cursor: RailNode | undefined;

  constructor(options: AnimatedSpriteContainerProperty) {
    super(options, RailNode);
    if (!this.model.isReadOnly()) {
      this.cursor = this.addChild({ oid: this.model.myid, ...cursorOpts });
    }
  }

  setInitialValues(props: { [index: string]: any }) {
    super.setInitialValues(props);
    if (this.cursor !== undefined) {
      this.cursor.current = undefined;
      this.cursor.destination = undefined;
      this.cursor.merge("pos", undefined);
    }
  }

  setupUpdateCallback() {
    super.setupUpdateCallback();
    if (this.cursor === undefined) {
      return;
    }
    this.addUpdateCallback("menu", v => {
      if (this.cursor === undefined) {
        return;
      }
      switch (v) {
        case MenuStatus.IDLE:
          this.cursor.merge("visible", false);
          break;
        case MenuStatus.SEEK_DEPARTURE:
        case MenuStatus.EXTEND_RAIL:
          this.cursor.merge("visible", true);
          break;
      }
    });
    this.addUpdateCallback("cursorClient", (v: Point | undefined) => {
      if (this.cursor === undefined) {
        return;
      }
      switch (this.props.menu) {
        case MenuStatus.SEEK_DEPARTURE:
        case MenuStatus.EXTEND_RAIL:
          this.cursor.merge("visible", v !== undefined);
          this.cursor.destination = v;
          this.cursor.moveDestination();
      }
    });
  }

  protected getChildOptions() {
    return this.getBasicChildOptions();
  }
}

const reDefaultValues: {
  from: number;
  to: number;
  eid: number;
} = {
  from: 0,
  to: 0,
  eid: 0
};

export class RailEdge extends AnimatedSpriteModel implements Monitorable {
  from: RailNode | undefined;
  to: RailNode | undefined;
  protected reverse: RailEdge | undefined;
  protected theta: number = 0;

  setupDefaultValues() {
    super.setupDefaultValues();
    this.addDefaultValues(reDefaultValues);
  }

  setupUpdateCallback() {
    super.setupUpdateCallback();
    this.addUpdateCallback("from", (fromID: number | undefined) => {
      let from = this.model.gamemap.get("rail_nodes", fromID) as
        | RailNode
        | undefined;
      this.resolveFrom(from);
    });
    this.addUpdateCallback("to", (toID: number | undefined) => {
      let to = this.model.gamemap.get("rail_nodes", toID) as
        | RailNode
        | undefined;
      this.resolveTo(to);
    });
  }

  resolve(error: ResolveError) {
    let from = this.model.gamemap.get("rail_nodes", this.props.from) as
      | RailNode
      | undefined;
    let to = this.model.gamemap.get("rail_nodes", this.props.to) as
      | RailNode
      | undefined;

    this.resolveFrom(from);
    this.resolveTo(to);

    let reverse = this.model.gamemap.get("rail_edges", this.props.eid) as
      | RailEdge
      | undefined;
    if (reverse !== undefined) {
      this.reverse = reverse;
    }
    return error;
  }

  protected resolveFrom(from: RailNode | undefined) {
    if (this.from !== from) {
      this.unlinkFrom();
    }
    this.linkFrom(from);
    this.updateVisible();
  }

  protected resolveTo(to: RailNode | undefined) {
    if (this.to !== to) {
      this.unlinkTo();
    }
    this.linkTo(to);
    this.updateVisible();
  }

  protected unlinkFrom() {
    if (this.from !== undefined) {
      delete this.from.out[this.props.id];
    }
    this.from = undefined;
  }

  protected unlinkTo() {
    if (this.to !== undefined) {
      delete this.to.in[this.props.id];
    }
    this.to = undefined;
  }

  protected linkFrom(from: RailNode | undefined) {
    if (this.from !== from) {
      this.from = from;
      if (from !== undefined) {
        from.out[this.props.id] = this;
        this.sprite.tint = from.get("tint");
      }
    }
  }

  protected linkTo(to: RailNode | undefined) {
    if (this.to !== to) {
      this.to = to;
      if (to !== undefined) {
        to.in[this.props.id] = this;
        this.sprite.tint = to.get("tint");
      }
    }
  }

  updateVisible() {
    if (this.from !== undefined && this.to !== undefined) {
      this.merge("visible", this.from.get("visible") && this.to.get("visible"));
    } else {
      this.merge("visible", false);
    }
    this.updateDisplayInfo();
  }

  updateDisplayInfo() {
    if (
      this.from !== undefined &&
      this.to !== undefined &&
      this.from.current !== undefined &&
      this.to.current !== undefined
    ) {
      let d = {
        x: this.to.current.x - this.from.current.x,
        y: this.to.current.y - this.from.current.y
      };
      let avg = {
        x: (this.from.current.x + this.to.current.x) / 2,
        y: (this.from.current.y + this.to.current.y) / 2
      };
      let theta = Math.atan2(d.y, d.x);
      this.current = {
        x: avg.x + graphicsOpts.slide * Math.cos(theta + Math.PI / 2),
        y: avg.y + graphicsOpts.slide * Math.sin(theta + Math.PI / 2)
      };

      this.sprite.rotation = theta;
      this.sprite.width = Math.sqrt(d.x * d.x + d.y * d.y);
      this.sprite.visible = true;
    } else {
      this.sprite.visible = false;
    }
    super.updateDisplayInfo();
  }

  shouldEnd() {
    if (this.from !== undefined && this.to !== undefined) {
      // 縮小時、集約先に行き着くまで描画する
      if (this.props.coord.zoom == -1) {
        return this.from.shouldEnd() && this.to.shouldEnd();
      }
    }
    return this.props.outMap;
  }
}

export class RailEdgeContainer
  extends AnimatedSpriteContainer<RailEdge, AnimatedSpriteProperty>
  implements MonitorContainer {
  deamon: RailNode | undefined;
  cursorOut: RailEdge | undefined;
  cursorIn: RailEdge | undefined;

  constructor(options: AnimatedSpriteContainerProperty) {
    super(options, RailEdge);
    if (!this.model.isReadOnly()) {
      this.cursorOut = this.addChild({
        ...cursorOpts,
        oid: this.model.myid,
        id: -2,
        from: -1,
        reverse: -3
      });
      this.cursorIn = this.addChild({
        ...cursorOpts,
        oid: this.model.myid,
        id: -3,
        to: -1,
        reverse: -2
      });
      this.cursorOut.resolve({});
      this.cursorIn.resolve({});
      this.deamon = this.cursorOut.from as RailNode;
    }
  }

  setupUpdateCallback() {
    super.setupUpdateCallback();
    if (this.model.isReadOnly()) {
      return;
    }

    this.addUpdateCallback("anchorObj", (v: PointModel | undefined) => {
      if (v instanceof RailNode || v === undefined) {
        this.setAnchor(v);
      }
    });
    this.addUpdateCallback("cursorObj", (v: PointModel | undefined) => {
      if (v instanceof RailNode) {
        this.setCursor(v);
      } else {
        this.setCursor(this.deamon);
      }
    });
  }

  protected setAnchor(anchor: RailNode | undefined) {
    let id = anchor !== undefined ? anchor.get("id") : undefined;
    if (this.cursorOut !== undefined && this.cursorIn !== undefined) {
      this.cursorOut.merge("to", id);
      this.cursorIn.merge("from", id);
    }
  }

  protected setCursor(cursor: RailNode | undefined) {
    let id = cursor !== undefined ? cursor.get("id") : undefined;
    if (this.cursorOut !== undefined && this.cursorIn !== undefined) {
      this.cursorOut.merge("from", id);
      this.cursorIn.merge("to", id);
    }
  }

  protected getChildOptions() {
    return this.getBasicChildOptions();
  }
}