import {Game} from "./Game.js"; import {directions} from "./Snake.js"; export class Tile { /** * A tile of the game grid * @param {int} x * @param {int} y * @param {tiles} type * @param {Game} game */ constructor(x, y, type = tiles.EMPTY, game) { if (typeof x === "number" && x >= 0 && x % 1 === 0) this.x = x; else throw new InvalidTileOption("x"); if (typeof y === "number" && y >= 0 && y % 1 === 0) this.y = y; else throw new InvalidTileOption("y"); if (type && Object.values(tiles).find(t => t === type)) this.type = type; else throw new InvalidTileOption("type"); if (game && game instanceof Game) this.game = game; else throw new InvalidTileOption("game"); } /** * Draw the tile on the grid */ draw() { const canvasPos = this.getCanvasPos(), size = this.getSize(); this.game.ctx.beginPath(); this.game.ctx.globalCompositeOperation = "destination-out"; this.game.ctx.fillRect(...canvasPos, ...size); this.game.ctx.globalCompositeOperation = "source-over"; const pxsize = Math.round(size[0]/12); const width = canvasPos[0]; const height = canvasPos[1]; switch (this.type) { case tiles.EMPTY: this.game.ctx.strokeStyle = "#999999"; this.game.ctx.rect(...canvasPos, ...size); break; case tiles.APPLE: if (!pxsize) { this.game.ctx.fillStyle = "#ff0000"; this.game.ctx.fillRect(...canvasPos, ...size); } else { this.game.ctx.fillStyle = "#00ffff"; this.game.ctx.fillRect(width+pxsize, height+pxsize, 10*pxsize, 10*pxsize); this.game.ctx.fillStyle = "#000000"; this.game.ctx.fillRect(width+4*pxsize, height, pxsize*4, pxsize); this.game.ctx.fillRect(width+4*pxsize, height+11*pxsize, pxsize*4, pxsize); for (const i of [2,5,8]) this.game.ctx.fillRect(width+i*pxsize, height+pxsize, pxsize*2, pxsize); for (const i of [1,4,7,10]) this.game.ctx.fillRect(width+i*pxsize, height+2*pxsize, pxsize, pxsize*2); this.game.ctx.fillRect(width, height+4*pxsize, pxsize, pxsize*4); this.game.ctx.fillRect(width+11*pxsize, height+4*pxsize, pxsize, pxsize*4); for (const i of [1,5,6,10]) this.game.ctx.fillRect(width+i*pxsize, height+8*pxsize, pxsize, pxsize*2); for (const i of [2,5,8]) this.game.ctx.fillRect(width+i*pxsize, height+10*pxsize, pxsize*2, pxsize); for (const i of [3,8,2,9]) this.game.ctx.fillRect(width+i*pxsize, height+4*pxsize, pxsize, pxsize); for (const i of [1,3,8,10]) this.game.ctx.fillRect(width+i*pxsize, height+6*pxsize, pxsize, pxsize); for (const i of [4,7]) this.game.ctx.fillRect(width+i*pxsize, height+7*pxsize, pxsize, pxsize); this.game.ctx.globalCompositeOperation = "destination-out"; for (const [i,j] of [[1,1],[10,1],[1,10],[10,10]]) this.game.ctx.fillRect(width+i*pxsize, height+j*pxsize, pxsize, pxsize); this.game.ctx.globalCompositeOperation = "source-over"; } break; case tiles.HEAD: if (!pxsize) { this.game.ctx.fillStyle = "#00ff5f"; this.game.ctx.fillRect(...canvasPos, ...size); } else { this.rotate(canvasPos, size); this.game.ctx.fillStyle = "#FF4294"; this.game.ctx.fillRect(width + 6 * pxsize, height + pxsize, 6 * pxsize, 10 * pxsize); this.game.ctx.fillRect(width + 4 * pxsize, height + 2 * pxsize, 6 * pxsize, 8 * pxsize); this.game.ctx.fillRect(width + 2 * pxsize, height + 3 * pxsize, 6 * pxsize, 6 * pxsize); this.game.ctx.fillRect(width + pxsize, height + 5 * pxsize, pxsize, 2 * pxsize); this.game.ctx.fillStyle = "#000000"; this.game.ctx.fillRect(width + 6 * pxsize, height, pxsize * 6, pxsize); this.game.ctx.fillRect(width + 6 * pxsize, height + 11 * pxsize, pxsize * 6, pxsize); this.game.ctx.fillRect(width + 4 * pxsize, height + pxsize, pxsize * 2, pxsize); this.game.ctx.fillRect(width + 4 * pxsize, height + 10 * pxsize, pxsize * 2, pxsize); this.game.ctx.fillRect(width + 2 * pxsize, height + 2 * pxsize, pxsize * 2, pxsize); this.game.ctx.fillRect(width + 2 * pxsize, height + 9 * pxsize, pxsize * 2, pxsize); this.game.ctx.fillRect(width + pxsize, height + 7 * pxsize, pxsize, pxsize * 2); this.game.ctx.fillRect(width + pxsize, height + 3 * pxsize, pxsize, pxsize * 2); this.game.ctx.fillRect(width, height + 5 * pxsize, pxsize, pxsize * 2); this.game.ctx.fillRect(width + 6 * pxsize, height + 4 * pxsize, pxsize * 4, pxsize); this.game.ctx.fillRect(width + 6 * pxsize, height + 7 * pxsize, pxsize * 4, pxsize); this.game.ctx.setTransform(1, 0, 0, 1, 0, 0); } break; case tiles.BODY: if (!pxsize) { this.game.ctx.fillStyle = "#0d5317"; this.game.ctx.fillRect(...canvasPos, ...size); } else { this.rotate(canvasPos, size); this.game.ctx.fillStyle = "#FF4294"; this.game.ctx.fillRect(width, height, 12*pxsize, 12*pxsize); this.game.ctx.fillStyle = "#000000"; this.game.ctx.fillRect(width, height, pxsize*12, pxsize); this.game.ctx.fillRect(width, height+11*pxsize, pxsize*12, pxsize); for (const [i,j] of [[1,1],[1,9],[2,3],[2,7],[3,5],[5,1],[5,9],[6,3],[6,7],[7,5],[9,1],[9,9],[10,3],[10,7],[11,5]]) this.game.ctx.fillRect(width+i*pxsize, height+j*pxsize, pxsize, pxsize*2); this.game.ctx.setTransform(1, 0, 0, 1, 0, 0); } break; case tiles.TAIL: if (!pxsize) { this.game.ctx.fillStyle = "#efff00"; this.game.ctx.fillRect(...canvasPos, ...size); } else { this.rotate(canvasPos, size); this.game.ctx.fillStyle = "#FF4294"; this.game.ctx.fillRect(width, height+pxsize, pxsize, 10*pxsize); this.game.ctx.fillRect(width+pxsize, height+2*pxsize, pxsize, 8*pxsize); this.game.ctx.fillRect(width+2*pxsize, height+3*pxsize, 2*pxsize, 6*pxsize); this.game.ctx.fillRect(width+4*pxsize, height+4*pxsize, 3*pxsize, 4*pxsize); this.game.ctx.fillRect(width+7*pxsize, height+5*pxsize, 4*pxsize, 2*pxsize); this.game.ctx.fillStyle = "#000000"; this.game.ctx.fillRect(width, height, pxsize, pxsize); this.game.ctx.fillRect(width, height+11*pxsize, pxsize, pxsize); this.game.ctx.fillRect(width+pxsize, height+pxsize, pxsize, pxsize); this.game.ctx.fillRect(width+pxsize, height+10*pxsize, pxsize, pxsize); this.game.ctx.fillRect(width+2*pxsize, height+2*pxsize, 2*pxsize, pxsize); this.game.ctx.fillRect(width+2*pxsize, height+9*pxsize, 2*pxsize, pxsize); this.game.ctx.fillRect(width+4*pxsize, height+3*pxsize, 3*pxsize, pxsize); this.game.ctx.fillRect(width+4*pxsize, height+8*pxsize, 3*pxsize, pxsize); this.game.ctx.fillRect(width+7*pxsize, height+4*pxsize, 4*pxsize, pxsize); this.game.ctx.fillRect(width+7*pxsize, height+7*pxsize, 4*pxsize, pxsize); this.game.ctx.fillRect(width+11*pxsize, height+5*pxsize, pxsize, 2*pxsize); this.game.ctx.fillRect(width+5*pxsize, height+5*pxsize, pxsize, 2*pxsize); this.game.ctx.fillRect(width, height+4*pxsize, pxsize, 4*pxsize); this.game.ctx.fillRect(width+pxsize, height+3*pxsize, pxsize, pxsize); this.game.ctx.fillRect(width+pxsize, height+8*pxsize, pxsize, pxsize); this.game.ctx.fillRect(width+4*pxsize, height+4*pxsize, pxsize, pxsize); this.game.ctx.fillRect(width+4*pxsize, height+7*pxsize, pxsize, pxsize); this.game.ctx.setTransform(1, 0, 0, 1, 0, 0); } break; case tiles.LIFE: this.game.ctx.fillStyle = "#e400ff"; this.game.ctx.fillRect(...canvasPos, ...size); break; } this.game.ctx.stroke(); } /** * Get the direction of a sprite depending of the next sprite * @returns {directions} */ getDirection() { const np = this.game.snake.body[this.game.snake.body.findIndex(([x,y]) => x === this.x && y === this.y)-1]; const d = [this.x, this.y].map((n, i) => -(n - np[i])); return Object.values(directions).find(([x, y]) => d[0] === x && d[1] === y); } /** * Rotate a sprite depending of the input direction or the next sprite * @param {[int, int]} canvasPos * @param {[int, int]} size */ rotate(canvasPos, size) { const cx = canvasPos[0] + 0.5 * size[0]; const cy = canvasPos[1] + 0.5 * size[1]; this.game.ctx.translate(cx, cy); switch (this.type === tiles.HEAD? this.game.lastDirection : this.getDirection()) { case directions.UP: this.game.ctx.rotate(Math.PI / 2); break; case directions.DOWN: this.game.ctx.rotate(-Math.PI / 2); break; case directions.RIGHT: this.game.ctx.rotate(-Math.PI); break; case directions.LEFT: break; } this.game.ctx.translate(-cx, -cy); } /** * Get the tile position on the canvas * @returns {[int, int]} */ getCanvasPos() { return [this.getSize()[0]*this.x, this.getSize()[1]*this.y] } /** * Get the tile size on the canvas * @returns {[int, int]} */ getSize() { const s = Math.min(Math.round(this.game.ctx.canvas.width/this.game.size[0]), Math.round(this.game.ctx.canvas.height/this.game.size[1])); return [s, s] } } export class InvalidTileOption extends Error { /** * @param {string} name */ constructor(name) { super(`Invalid Tile option: ${name}`); } } export const tiles = { EMPTY: "empty", APPLE: "apple", HEAD: "head", BODY: "body", TAIL: "tail", LIFE: "life" };