import { Position, Size, Velocity } from './types'; import { between } from './utils'; export abstract class Element { instance: HTMLElement; elements: Element[] = []; style: CSSStyleDeclaration; isDestroyed = false; protected constructor(public size: Size, public pos: Position, public vel: Velocity = Velocity.create()) { this.instance = document.createElement('div'); this.style = this.instance.style; this.style.transition = 'transform linear 20ms'; this.translate = this.translate.bind(this); this.bounce = this.bounce.bind(this); this.update = this.update.bind(this); this.reset = this.reset.bind(this); this.destroy = this.destroy.bind(this); this.intersects = this.intersects.bind(this); } destroy(): this { this.isDestroyed = true; return this; } get top(): number { return this.pos.y; } get right(): number { return this.pos.x + this.size.width; } get bottom(): number { return this.pos.y + this.size.height; } get left(): number { return this.pos.x; } appendElement(element: Element): this { this.elements.push(element); this.instance.appendChild(element.instance); return this; } removeElement(element: Element): this { this.elements.splice(this.elements.indexOf(element), 1); this.instance.removeChild(element.instance); return this; } removeAllElements(): this { while (this.instance.childNodes.length) { this.instance.removeChild(this.instance.childNodes[0]); } this.elements = []; return this; } move(pos: Position): this { this.pos = pos; return this; } translate(): this { this.style.transform = `translate(${this.pos.x}px, ${this.pos.y}px)`; return this; } isInBoundsX(x: number, w: number): boolean { return x >= 0 && x + w <= this.size.width; } isInBoundsY(y: number, h: number): boolean { return y >= 0 && y + h <= this.size.height; } isInBounds(pos: Position, size: Size): boolean { return this.isInBoundsX(pos.x, size.width) && this.isInBoundsY(pos.y, size.height); } intersectsTop(element: Element): boolean { let yIntersects = false; let xIntersects = false; if (between(element.bottom, this.top, this.top + Math.round(this.size.height / 2))) { yIntersects = true; } if (between(element.left, this.left, this.right) || between(element.right, this.left, this.right)) { xIntersects = true; } return yIntersects && xIntersects; } intersectsBottom(element: Element): boolean { let yIntersects = false; let xIntersects = false; if (between(element.top, this.bottom, this.bottom - Math.round(this.size.height / 2))) { yIntersects = true; } if (between(element.left, this.left, this.right) || between(element.right, this.left, this.right)) { xIntersects = true; } return yIntersects && xIntersects; } intersectsLeft(element: Element): boolean { let yIntersects = false; let xIntersects = false; if (between(element.right, this.left, this.left + Math.round(this.size.width / 2))) { xIntersects = true; } if (between(element.top, this.top, this.bottom) || between(element.bottom, this.top, this.bottom)) { yIntersects = true; } return yIntersects && xIntersects; } intersectsRight(element: Element): boolean { let yIntersects = false; let xIntersects = false; if (between(element.left, this.right - Math.round(this.size.width / 2), this.right)) { xIntersects = true; } if (between(element.top, this.top, this.bottom) || between(element.bottom, this.top, this.bottom)) { yIntersects = true; } return yIntersects && xIntersects; } intersects(element: Element): boolean { return ( this.intersectsTop(element) || this.intersectsRight(element) || this.intersectsLeft(element) || this.intersectsBottom(element) ); } bounce(element: Element): boolean { if (this.intersectsTop(element)) { element.vel.y = Math.abs(element.vel.y) * -1; return true; } else if (this.intersectsBottom(element)) { element.vel.y = Math.abs(element.vel.y); return true; } else if (this.intersectsLeft(element)) { element.vel.x = Math.abs(element.vel.x) * -1; return true; } else if (this.intersectsRight(element)) { element.vel.x = Math.abs(element.vel.x); return true; } return false; } update(): this { this.elements.forEach(element => { if (element.isDestroyed) { this.removeElement(element); } else { element.update(); } }); return this; } reset(): this { this.elements.forEach(element => element.reset()); return this; } }