3
\$\begingroup\$

This is my first foray into a full TypeScript program, and one I decided to do for fun and without the aid of jQuery. It is also my first time developing with the HTML canvas. That's quite a lot of firsts, and there must be things I can do better.

HexCells is one of my absolute favorite games and the last version included the ability to import levels that could be created via a fan-made editor. I had a burning desire to make my own editor, so here's where I've started.

The following controls have been implemented thus far:

  • E: switch between edit and init modes
  • Left-click: cycle the cell type in edit mode and cycle whether initially covered in init mode
  • Right-click: cycle the number type
  • Shift+right-click an empty cell: cycle which side displays a number

A live implementation can be found here.

The hex grid implementation is largely based on this excellent resource at Red Blob Games.

After poking around at various libraries, I decided to use CreateJS for the drawing.

App.ts

/* tslint:disable:no-console */

import 'lib/easeljs';
import { Board } from './Board';
import { HexBase } from './HexBase';
import { OrientationType } from './Orientation';
import { Point } from './Point';
import { Size } from './Size';

// Should enums get their own file?
export enum AppMode {
  Edit,
  EditInit,
  Play,
}

export class App {

  public static canvas: HTMLCanvasElement;
  public static stage: createjs.Stage;
  public static board: Board;
  public static mode: AppMode = AppMode.Edit;

  public static width(): number {
    return this.canvas.clientWidth * 0.9;
  }

  public static height(): number {
    return this.canvas.clientHeight * 0.9;
  }

  public static start(canvas: HTMLCanvasElement) {

    App.canvas = canvas;
    App.stage = new createjs.Stage(canvas);
    App.board = new Board(new Size(10, 10));
    for (const key in App.board.hexes) {
      if (App.board.hexes.hasOwnProperty(key)) {
        App.board.hexes[key].randomize();
      }
    }
    // App.board.import(App.importString);

    canvas.oncontextmenu = () => false;

    window.addEventListener('resize', () => {
      App.draw();
    });

    window.addEventListener('keypress', (event: KeyboardEvent) => {
      if (event.key === 'e') {
        if (App.mode !== AppMode.Edit)
          App.mode = AppMode.Edit;
        else
          App.mode = AppMode.EditInit;
        App.board.redraw();
        App.stage.update();
      }
    });

    App.board.draw();
    App.draw();
  }

  public static draw() {
    App.canvas.width = App.canvas.parentElement.offsetWidth;
    App.canvas.height = App.canvas.parentElement.offsetHeight;
    App.board.resize();
    App.board.redraw();
    App.stage.update();
  }
}

Board.ts

import 'lib/easeljs';
import { App } from './App';
import { Hex } from './Hex';
import { HexBase } from './HexBase';
import { Orientation, OrientationType } from './Orientation';
import { Point } from './Point';
import { Size } from './Size';

export class Board {

  public background: createjs.Shape = new createjs.Shape();
  public hexes: { [key: string]: Hex } = {};
  public author: string;
  public title: string;
  public info: string;
  public hexSize: Size;

  private orientation = Orientation.flat;
  private origin: Point = new Point(0, 0);

  public constructor(
    public hexCount: Size,
  ) {
    this.init(hexCount.width, hexCount.height);
  }

  public init(width: number, height: number) {
    this.hexCount = new Size(width, height);
    this.hexes = {};
    this.resize();
    const i1 = -Math.floor(height / 2);
    const i2 = i1 + height;
    const j1 = -Math.floor(width / 2);
    const j2 = j1 + width;
    for (let j = j1; j < j2; j++) {
      const jOffset = -Math.floor(j / 2);
      for (let i = i1 + jOffset; i < i2 + jOffset; i++) {
        const hex = HexBase.fromSQ(i, j);
        this.hexes[hex.hash()] = Hex.fromHexBase(hex);
      }
    }
    for (const key in this.hexes) {
      if (this.hexes.hasOwnProperty(key)) {
        const hex = this.hexes[key];
        hex.neighbors = [];
        for (let i = 0; i < HexBase.directions.length; i++) {
          const n = hex.neighbor(i);
          hex.neighbors.push(this.getHex(n.q, n.r));
        }
      }
    }
  }

  public getHex(q: number, r: number): Hex {
    return this.hexes[HexBase.fromQR(q, r).hash()];
  }

  public resize() {
    this.hexSize = Size.square(Math.min(
      Math.floor(App.width() / this.hexCount.width / 1.5),
      Math.floor(App.height() / (Math.sqrt(3) * this.hexCount.height)),
    ));
    const canvas = App.canvas;
    const width = canvas.clientWidth;
    const height = canvas.clientHeight;
    const offset = this.centerOffset();
    App.stage.setTransform(width / 2 - offset.x, height / 2 - offset.y);
  }

  public redraw() {
    for (const key in this.hexes) {
      if (this.hexes.hasOwnProperty(key)) {
        this.hexes[key].draw();
      }
    }
  }

  public draw() {
    const canvas = App.canvas;
    const width = canvas.clientWidth;
    const height = canvas.clientHeight;
    const offset = this.centerOffset();
    this.background.graphics.beginFill('#e7e7e7').drawRect(0, 0, width, height);
    App.stage.setTransform(width / 2 - offset.x, height / 2 - offset.y);
    for (const key in this.hexes) {
      if (this.hexes.hasOwnProperty(key)) {
        App.stage.addChild(this.hexes[key].shape);
        App.stage.addChild(this.hexes[key].text);
        this.hexes[key].draw();
      }
    }
  }

  public import(s: string): boolean {

    const lineStrings = s.split('\n');
    if (lineStrings.length < 6)
      return false;

    const title = lineStrings[1];
    const author = lineStrings[2];
    let info = lineStrings[3];
    if (lineStrings[4].replace(/\s/, '').length > 0)
      info += '\n' + lineStrings[4];
    lineStrings.splice(0, 5);

    for (let i = lineStrings.length - 1; i >= 0; i--) {
      lineStrings[i] = lineStrings[i].replace(/\s/, '');
      if (lineStrings[i].length === 0)
        lineStrings.splice(i, 1);
    }
    if (lineStrings.length === 0)
      return false;
    const len = lineStrings[0].length;
    if (len % 2 !== 0)
      return false;
    for (const lineString of lineStrings) {
      if (lineString.length !== len)
        return false;
      if (/[^oOxX\\\|\/\[\-\]\.\+cn]/.test(lineString))
        return false;
    }

    this.init(len / 2, Math.ceil(lineStrings.length / 2));
    const lines = [];
    for (let k = -1; k < lineStrings.length; k += 2) {
      let line1 = '';
      if (k > -1)
        line1 = lineStrings[k];
      let line2 = '';
      if (k + 1 < lineStrings.length)
        line2 = lineStrings[k + 1];
      const cOffset = -Math.floor(len / 4);
      const r = Math.floor(lineStrings.length / 4) - Math.floor((k + 1) / 2);
      for (let i = 0; i < len; i += 2) {
        const lineT = i % 4 === 0 ? line2 : line1;
        const st = lineT === '' ? '..' : lineT.substr(i, 2);
        const h = Hex.fromAxial(i / 2 + cOffset, r);
        const h2 = this.getHex(h.q, h.r);
        h2.import(st);
      }
    }
    this.draw();
    App.stage.update();
  }

  public hexToPixel(hex: HexBase): Point {
    const o = this.orientation;
    const x = (o.f0 * hex.q + o.f1 * hex.r) * this.hexSize.width;
    const y = (o.f2 * hex.q + o.f3 * hex.r) * this.hexSize.height;
    return new Point(x + this.origin.x, y + this.origin.y);
  }

  public pixelToHex(point: Point): HexBase {
    const o = this.orientation;
    const p = new Point(
      (point.x - this.origin.x) / this.hexSize.width,
      (point.y - this.origin.y) / this.hexSize.height);
    const q = o.b0 * p.x + o.b1 * p.y;
    const r = o.b2 * p.x + o.b3 * p.y;
    return new HexBase(q, r, -q - r);
  }

  public hexCornerOffset(corner: number, size: Size = this.hexSize): Point {
    const o = this.orientation;
    const angle = 2 * Math.PI * (o.startAngle + corner) / 6;
    return new Point(size.width * Math.cos(angle), size.height * Math.sin(angle));
  }

  public hexSideOffset(side: number, size: Size = this.hexSize): Point {
    const o = this.orientation;
    const angle = 2 * Math.PI * (o.startAngle + 0.5 + side) / 6;
    return new Point(size.width * Math.cos(angle), size.height * Math.sin(angle));
  }

  public hexSideAngle(side: number, size: Size = this.hexSize): number {
    const o = this.orientation;
    return 2 * Math.PI * (o.startAngle + 0.5 + side) / 6;
  }

  public hexCorners(hex: HexBase, size: Size = this.hexSize): Point[] {
    const corners: Point[] = [];
    const center = this.hexToPixel(hex);
    for (let i = 0; i < 6; i++) {
      const offset = this.hexCornerOffset(i, size);
      corners.push(new Point(center.x + offset.x, center.y + offset.y));
    }
    return corners;
  }

  private centerOffset(): Point {
    const c1 = new Point(0, 0);
    const c2 = new Point(0, 0);
    for (const key in this.hexes) {
      if (this.hexes.hasOwnProperty(key)) {
        const hex = this.hexes[key];
        const corners = this.hexCorners(hex);
        for (const corner of corners) {
          if (corner.x < c1.x) { c1.x = corner.x; }
          if (corner.y < c1.y) { c1.y = corner.y; }
          if (corner.x > c2.x) { c2.x = corner.x; }
          if (corner.y > c2.y) { c2.y = corner.y; }
        }
      }
    }
    return new Point(
      Math.round((c2.x - Math.abs(c1.x)) / 2),
      Math.round((c2.y - Math.abs(c1.y)) / 2),
    );
  }

}

HexBase.ts

/* tslint:disable:no-bitwise */

import 'lib/easeljs';
import { Point } from './Point';

export class HexBase {

  public static directions: HexBase[] = [
    new HexBase(1, 0, -1),
    new HexBase(1, -1, 0),
    new HexBase(0, -1, 1),
    new HexBase(-1, 0, 1),
    new HexBase(-1, 1, 0),
    new HexBase(0, 1, -1),
  ];

  public static fromQR(q: number, r: number): HexBase {
    return new HexBase(q, r, -q - r);
  }

  public static fromQS(q: number, s: number): HexBase {
    return new HexBase(q, -q - s, s);
  }

  public static fromRS(r: number, s: number): HexBase {
    return new HexBase(-r - s, r, s);
  }

  public static fromRQ(r: number, q: number): HexBase {
    return new HexBase(q, r, -q - r);
  }

  public static fromSQ(s: number, q: number): HexBase {
    return new HexBase(q, -q - s, s);
  }

  public static fromSR(s: number, r: number): HexBase {
    return new HexBase(-r - s, r, s);
  }

  public static fromAxial(col: number, row: number): HexBase {
    const q = col;
    const s = row - (col - (col & 1)) / 2;
    return HexBase.fromQS(q, s);
  }

  public sideCount: number[] = [];
  public surroundCount: number = 0;
  public extendCount: number = 0;

  constructor(
    public q: number,
    public r: number,
    public s: number,
  ) {
    for (let i = 0; i < 6; i++) {
      this.sideCount.push(0);
    }
  }

  public toAxial(): Point {
    const col = this.q;
    const row = this.s + (this.q - (this.q & 1)) / 2;
    return new Point(col, row);
  }

  public hash(): string {
    return this.q + ',' + this.r;
  }

  public add(h: HexBase): HexBase {
    return new HexBase(this.q + h.q, this.r + h.r, this.s + h.s);
  }

  public neighbor(direction: number): HexBase {
    return this.add(HexBase.directions[direction]);
  }

  public round(): HexBase {
    let q = Math.floor(Math.round(this.q));
    let r = Math.floor(Math.round(this.r));
    let s = Math.floor(Math.round(this.s));
    const qD = Math.abs(q - this.q);
    const rD = Math.abs(r - this.r);
    const sD = Math.abs(s - this.s);
    if (qD > rD && qD > sD) {
      q = -r - s;
    } else {
      if (rD > sD) {
        r = -q - s;
      } else {
        s = -q - r;
      }
    }
    return new HexBase(q, r, s);
  }

}

Hex.ts

import { App, AppMode } from './App';
import { HexBase } from './HexBase';
import { Point } from './Point';
import { Settings } from './Settings';

export enum HexType {
  Invisible,
  Normal,
  Blue,
}

export enum HexCountType {
  Plain,
  Invisible,
  Extended,
}

export class Hex extends HexBase {

  public static fromHexBase(h: HexBase): Hex {
    return new Hex(h.q, h.r, h.s);
  }

  public type: HexType = HexType.Invisible;
  public normalCountType: HexCountType = HexCountType.Plain;
  public blueCountType: HexCountType = HexCountType.Invisible;
  public invisibleCountType: HexCountType = HexCountType.Plain;
  public shape: createjs.Shape = new createjs.Shape();
  public text: createjs.Text = new createjs.Text();
  public covered: boolean = false;
  public sideCountDirection: number = -1;
  public neighbors: Hex[] = [];

  constructor(
    public q: number,
    public r: number,
    public s: number,
  ) {
    super(q, r, s);
    this.addListeners();
  }

  public updateCounts(amount: number) {
    for (let i = 0; i < 6; i++) {
      let n = this.neighbors[i];
      if (n !== undefined) {
        n.surroundCount += amount;
        n.extendCount += amount;
        if (n.neighbors[i] !== undefined) {
          n.neighbors[i].extendCount += amount;
        }
        let j = i === 5 ? 0 : i + 1;
        if (n.neighbors[j] !== undefined) {
          n.neighbors[j].extendCount += amount;
        }
        j = (i - 3  + 6) % 6;
        while (n !== undefined) {
          n.sideCount[j] += amount;
          n = n.neighbors[i];
        }
      }
    }
  }

  public cycleType() {
    let newType = HexType.Normal;
    switch (this.type) {
      case HexType.Normal: newType = HexType.Blue; break;
      case HexType.Blue: newType = HexType.Invisible; break;
    }
    this.changeType(newType);
    this.drawAllNeighbors();
    this.draw();
    App.stage.update();
  }

  public cycleSideCountSide() {
    if (this.type !== HexType.Invisible)
      return;
    let i = this.sideCountDirection;
    i = i === -1 ? 5 : i - 1;
    while (i !== -1 && this.neighbors[i] === undefined) {
      i = i === -1 ? 5 : i - 1;
    }
    this.sideCountDirection = i;
    if (i > -1 && this.invisibleCountType === HexCountType.Invisible)
      this.invisibleCountType = HexCountType.Plain;
    this.draw();
    App.stage.update();
  }

  public cycleCountType() {
    switch (this.type) {
      case HexType.Normal:
        switch (this.normalCountType) {
          case HexCountType.Invisible:
            this.normalCountType = HexCountType.Plain;
            break;
          case HexCountType.Plain:
            if (this.surroundCount > 1)
              this.normalCountType = HexCountType.Extended;
            else
              this.normalCountType = HexCountType.Invisible;
            break;
          case HexCountType.Extended:
            this.normalCountType = HexCountType.Invisible;
            break;
        }
      case HexType.Blue:
        if (this.blueCountType === HexCountType.Plain)
          this.blueCountType = HexCountType.Invisible;
        else
          this.blueCountType = HexCountType.Plain;
        break;
      case HexType.Invisible:
        if (this.sideCountDirection === -1) {
          this.cycleSideCountSide();
        } else {
          switch (this.invisibleCountType) {
            case HexCountType.Invisible:
              this.invisibleCountType = HexCountType.Plain;
              break;
            case HexCountType.Plain:
              if (this.sideCount[this.sideCountDirection] > 1)
                this.invisibleCountType = HexCountType.Extended;
              else
                this.invisibleCountType = HexCountType.Invisible;
              break;
            case HexCountType.Extended:
              this.invisibleCountType = HexCountType.Invisible;
              break;
          }
        }
        break;
    }
    this.draw();
    App.stage.update();
  }

  public drawAllNeighbors() {
    for (let i = 0; i < 6; i++) {
      let n = this.neighbors[i];
      if (n !== undefined) {
        if (n.neighbors[i] !== undefined) {
          n.neighbors[i].draw();
        }
        const j = i === 5 ? 0 : i + 1;
        if (n.neighbors[j] !== undefined) {
          n.neighbors[j].draw();
        }
        while (n !== undefined) {
          n.draw();
          n = n.neighbors[i];
        }
      }
    }
  }

  public draw() {
    const p = App.board.hexToPixel(this);
    this.drawHex(p);
    this.drawText(p);
  }

  public changeType(type: HexType) {
    const prevType = this.type;
    this.type = type;
    if (prevType === HexType.Blue && type !== HexType.Blue) {
      this.updateCounts(-1);
    } else if (prevType !== HexType.Blue && type === HexType.Blue) {
      this.updateCounts(1);
    }
  }

  public changeCountType(type: HexCountType) {
    switch (this.type) {
      case HexType.Normal:
        this.normalCountType = type;
        break;
      case HexType.Blue:
        this.blueCountType = type === HexCountType.Invisible ? type : HexCountType.Plain;
        break;
      case HexType.Invisible:
        this.invisibleCountType = type;
        break;
    }
  }

  public randomize() {
    const types = [ HexType.Invisible, HexType.Normal, HexType.Blue ];
    this.changeType(types[Math.floor(Math.random() * 3)]);
    const countTypes = [ HexCountType.Plain, HexCountType.Invisible, HexCountType.Extended ];
    this.changeCountType(countTypes[Math.floor(Math.random() * 3)]);
  }

  public import(s: string) {
    const typeS = s.substr(0, 1);
    const countS = s.substr(1, 1);
    this.covered = false;
    switch (typeS) {
      case '.':  this.changeType(HexType.Invisible); break;
      case 'o':  this.changeType(HexType.Normal);    this.covered = true; break;
      case 'O':  this.changeType(HexType.Normal);    break;
      case 'x':  this.changeType(HexType.Blue);      this.covered = true; break;
      case 'X':  this.changeType(HexType.Blue);      break;
      case '\\': this.changeType(HexType.Invisible); this.sideCountDirection = 0; break;
      case '[':  this.changeType(HexType.Invisible); this.sideCountDirection = 1; break;
      case '-':  this.changeType(HexType.Invisible); this.sideCountDirection = 2; break;
      case ']':  this.changeType(HexType.Invisible); this.sideCountDirection = 3; break;
      case '/':  this.changeType(HexType.Invisible); this.sideCountDirection = 4; break;
      case '|':  this.changeType(HexType.Invisible); this.sideCountDirection = 5; break;
    }
    switch (countS) {
      case '.': this.changeCountType(HexCountType.Invisible); break;
      case '+': this.changeCountType(HexCountType.Plain);     break;
      case 'c': this.changeCountType(HexCountType.Extended);  break;
      case 'n': this.changeCountType(HexCountType.Extended);  break;
    }
  }

  private color() {
    let color = Settings.hexColors.normal;
    if (this.covered && App.mode !== AppMode.Edit) {
      color = Settings.hexColors.covered;
    } else {
      switch (this.type) {
        case HexType.Invisible:
          if (App.mode === AppMode.Edit)
            color = Settings.hexColors.invisibleEditing;
          else
            color = Settings.hexColors.invisible;
          break;
        case HexType.Blue:
          color = Settings.hexColors.blue;
          break;
      }
    }
    return color;
  }

  private drawHex(p: Point) {
    const color = this.color();
    const g = this.shape.graphics;
    g.clear();
    const size = App.board.hexSize;
    g.beginFill('white').drawPolyStar(p.x, p.y, size.width * 0.96, 6, 0, 0);
    g.beginFill(color.border).drawPolyStar(p.x, p.y, size.width * 0.92, 6, 0, 0);
    g.beginFill(color.fill).drawPolyStar(p.x, p.y, size.width * 0.75, 6, 0, 0);
  }

  private drawText(p: Point) {
    this.text.x = p.x;
    this.text.y = p.y;
    this.text.text = '';
    this.text.textBaseline = 'middle';
    this.text.textAlign = 'center';
    this.text.rotation = 0;
    if (this.covered && App.mode !== AppMode.Edit)
      return;
    switch (this.type) {
      case HexType.Normal: this.drawTextNormal(); break;
      case HexType.Blue: this.drawTextBlue(); break;
      case HexType.Invisible: this.drawTextInvisible(); break;
    }
  }

  private drawTextNormal() {
    this.text.font = (App.board.hexSize.width * Settings.hexFontScales.normal).toString() + 'px Harabara';
    this.text.color = Settings.hexColors.normal.text;
    if (this.normalCountType === HexCountType.Invisible) {
      this.text.text = '?';
    } else {
      this.text.text = this.surroundCount.toString();
      if (this.normalCountType === HexCountType.Extended && this.surroundCount > 1) {
        let consecutive = true;
        if (this.surroundCount < 5) {
          let i = 0;
          for (;
            this.neighbors[i] !== undefined && this.neighbors[i].type === HexType.Blue;
            i++);
          for (;
            this.neighbors[i] === undefined || this.neighbors[i].type !== HexType.Blue;
            i = i === 5 ? 0 : i + 1);
          let j = 0;
          for (let k = 0; k < this.surroundCount; k++) {
            j += this.neighbors[i] !== undefined && this.neighbors[i].type === HexType.Blue ? 1 : 0;
            i = i === 5 ? 0 : i + 1;
          }
          consecutive = j === this.surroundCount;
        }
        if (consecutive)
          this.text.text = '{' + this.text.text + '}';
        else
          this.text.text = '-' + this.text.text + '-';
      }
    }
  }

  private drawTextBlue() {
    if (this.blueCountType !== HexCountType.Invisible) {
      this.text.font = (App.board.hexSize.width * Settings.hexFontScales.blue).toString() + 'px Harabara';
      this.text.color = Settings.hexColors.blue.text;
      this.text.text = this.extendCount.toString();
    }
  }

  private drawTextInvisible() {
    if (this.sideCountDirection > -1 && this.invisibleCountType !== HexCountType.Invisible) {
      const size = App.board.hexSize;
      const d = (6 - this.sideCountDirection) % 6;
      const pOffset = App.board.hexSideOffset(d, size.scale(0.5));
      this.text.x += pOffset.x;
      this.text.y += pOffset.y;
      this.text.font = (size.width * Settings.hexFontScales.invisible).toString() + 'px Harabara';
      this.text.rotation = App.board.hexSideAngle(d) * 180 / Math.PI - 90;
      this.text.color = Settings.hexColors.invisible.text;
      this.text.text = this.sideCount[this.sideCountDirection].toString();
      if (this.invisibleCountType === HexCountType.Extended && this.sideCount[this.sideCountDirection] > 1) {
        const i = this.sideCountDirection;
        let c = 0;
        let n = this.neighbors[i];
        while (n !== undefined && n.type !== HexType.Blue)
          n = n.neighbors[i];
        while (n !== undefined && n.type !== HexType.Normal && c < this.sideCount[i]) {
          if (n.type === HexType.Blue)
            c++;
          n = n.neighbors[i];
        }
        if (c === this.sideCount[i])
          this.text.text = '{' + this.text.text + '}';
        else
          this.text.text = '-' + this.text.text + '-';
      }
    }
  }

  private addListeners() {
    this.shape.addEventListener('mousedown', (event: createjs.MouseEvent) => {
      if (event.nativeEvent.which === 1) {
        this.handleLeftClick(event);
      } else if (event.nativeEvent.which === 3) {
        this.handleRightClick(event);
      }
    });
  }

  private handleLeftClick(event: createjs.MouseEvent) {
    if (App.mode === AppMode.Edit) {
      this.cycleType();
    } else if (App.mode === AppMode.EditInit && this.type !== HexType.Invisible) {
      this.covered = !this.covered;
      this.draw();
      App.stage.update();
    }
  }

  private handleRightClick(event: createjs.MouseEvent) {
    if (App.mode === AppMode.Edit) {
      if (this.type === HexType.Invisible && event.nativeEvent.shiftKey) {
        this.cycleSideCountSide();
      } else {
        this.cycleCountType();
      }
    }
  }
}

Orientation.ts

export enum OrientationType {
  Pointy,
  Flat,
}

export class Orientation {

  public static pointy: Orientation = new Orientation(
    Math.sqrt(3),
    Math.sqrt(3) / 2,
    0,
    3 / 2,
    Math.sqrt(3) / 3,
    -1 / 3,
    0,
    2 / 3,
    0.5,
  );

  public static flat: Orientation = new Orientation(
    3 / 2,
    0,
    Math.sqrt(3) / 2,
    Math.sqrt(3),
    2 / 3,
    0,
    -1 / 3,
    Math.sqrt(3) / 3,
    0,
  );

  constructor(
    public f0: number,
    public f1: number,
    public f2: number,
    public f3: number,
    public b0: number,
    public b1: number,
    public b2: number,
    public b3: number,
    public startAngle: number,
  ) { }

}

Point.ts

export class Point {
  constructor(
    public x: number,
    public y: number,
  ) { }
}

Settings.ts

// Is this a bad idea?
export class Settings {
  public static hexColors = {
    blue            : { border: "#149cd8",     fill: "#05a4eb",     text: "white"   },
    covered         : { border: "#ff9f00",     fill: "#ffaf29",     text: "white"   },
    invisible       : { border: "transparent", fill: "transparent", text: "#464646" },
    invisibleEditing: { border: "#f0f0f0",     fill: "white",       text: "#464646" },
    normal          : { border: "#2c2f31",     fill: "#3e3e3e",     text: "white"   },
  };
  public static hexFontScales = {
    blue            : 0.72,
    invisible       : 0.64,
    normal          : 0.72,
  };
}

Size.ts

export class Size {

  public static square(width: number) {
    return new Size(width, width);
  }

  constructor(
    public width: number,
    public height: number,
  ) { }

  public scale(value: number) {
    return new Size(this.width * value, this.height * value);
  }
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.