export interface IGame { update(): void; draw(): void; } export class Color { readonly r: number; readonly g: number; readonly b: number; readonly a: number; constructor(r: number, g: number, b: number, a?: number) { this.r = r; this.g = g; this.b = b; this.a = a ?? 255; } static parseHexCode(hexCode: string) { const regex1 = /#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})?/; const regex2 = /#([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})?/; let result = regex1.exec(hexCode) ?? regex2.exec(hexCode); if (result == null) { throw `could not parse color: ${hexCode}` } let parseGroup = (s: string | undefined): number => { if (s === undefined) { return 255; } if (s.length == 1) { return 17 * parseInt(s, 16); } return parseInt(s, 16); } return new Color( parseGroup(result[1]), parseGroup(result[2]), parseGroup(result[3]), parseGroup(result[4]), ); } toStyle(): string { return `rgba(${this.r}, ${this.g}, ${this.b}, ${this.a / 255.0})` } } export class Point { readonly x: number; readonly y: number; constructor(x: number, y: number) { this.x = x; this.y = y; } offset(other: Point | Size): Point { if (other instanceof Point) { return new Point(this.x + other.x, this.y + other.y); } return new Point(this.x + other.w, this.y + other.h); } negate() { return new Point(-this.x, -this.y); } equals(other: Point): boolean { return this.x == other.x && this.y == other.y; } } export class Size { readonly w: number; readonly h: number; constructor(w: number, h: number) { this.w = w; this.h = h; } add(other: Size) { return new Size(this.w + other.w, this.h + other.h); } } export class Rect { readonly top: Point; readonly size: Size; constructor(top: Point, size: Size) { this.top = top; this.size = size; } offset(offset: Point) { return new Rect(this.top.offset(offset), this.size); } contains(other: Point) { return (other.x >= this.top.x && other.y >= this.top.y && other.x < this.top.x + this.size.w && other.y < this.top.y + this.size.h); } } export class Grid { readonly size: Size; #data: T[][]; constructor(size: Size, cbDefault: (xy: Point) => T) { this.size = size; this.#data = []; for (let y = 0; y < size.h; y++) { let row = []; for (let x = 0; x < size.w; x++) { row.push(cbDefault(new Point(x, y))) } this.#data.push(row); } } static createGridFromStringArray(ary: Array): Grid { let w = 0; let h = ary.length; for (let i = 0; i < h - 1; i++) { let w1 = ary[i].length; let w2 = ary[i + 1].length; if (w1 != w2) { throw `createGridFromStringArray: must be grid-shaped, got ${ary}` } w = w1; } return new Grid( new Size(w, h), (xy) => { return ary[xy.y].charAt(xy.x); } ) } static createGridFromJaggedArray(ary: Array>): Grid { let w = 0; let h = ary.length; for (let i = 0; i < h - 1; i++) { let w1 = ary[i].length; let w2 = ary[i + 1].length; if (w1 != w2) { throw `createGridFromJaggedArray: must be grid-shaped, got ${ary}` } w = w1; } return new Grid( new Size(w, h), (xy) => { return ary[xy.y][xy.x]; } ) } map(cbCell: (content: T, position: Point) => T2) { return new Grid(this.size, (xy) => cbCell(this.get(xy), xy)); } #checkPosition(position: Point) { if ( (position.x < 0 || position.x >= this.size.w || Math.floor(position.x) != position.x) || (position.y < 0 || position.y >= this.size.h || Math.floor(position.y) != position.y) ) { throw `invalid position for ${this.size}: ${position}` } } get(position: Point): T { this.#checkPosition(position); return this.#data[position.y][position.x]; } set(position: Point, value: T) { this.#checkPosition(position); this.#data[position.y][position.x] = value; } } export enum AlignX { Left = 0, Center = 1, Right = 2 } export enum AlignY { Top = 0, Middle = 1, Bottom = 2, }