199 lines
4.2 KiB
TypeScript
199 lines
4.2 KiB
TypeScript
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<T> {
|
|
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<string>): Grid<string> {
|
|
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<T>(ary: Array<Array<T>>): Grid<T> {
|
|
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<T2>(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,
|
|
}
|