import { Theme } from "../Theme.js" import Tile2 from "./Tile2.js" import AsyncTween from "../../AsyncTween.js" import { Easing } from "../../Easing.js" import { Point } from "../../spatial/Point.js" import { ActionQueue, MatchTilesAction, SpawnTileAction, SwapTileAction, TileFallAction } from "../../ActionQueue.js" class Board { constructor() { this.tiles = [] this.position = new Point(0, 0) this.actionQueue = new ActionQueue() this.selectedTiles = [] } init(canvasBounds) { this.boardSize = { x: 7, y: 7 } for (let y = 0; y < this.boardSize.y; y++) { for (let x = 0; x < this.boardSize.x; x++) { this.spawnTile(x, y) } } this.onResize(canvasBounds) // setTimeout(() => { // console.log("checking for matches on load") this.checkForMatches() // }, 2000) } spawnTile(x, y) { const tileType = Math.floor(Math.random() * Object.values(Theme.Colors.TileColors).length) const newTile = new Tile2(this, x, y, tileType) this.tiles.push(newTile) return newTile } draw(ctx) { ctx.save() ctx.translate(-this.position.x, -this.position.y) ctx.fillStyle = Theme.Colors.umber ctx.beginPath() ctx.rect(0, 0, this.boardSize.x * (Board.TILE_PADDING + Board.TILE_SIZE), this.boardSize.y * (Board.TILE_PADDING + Board.TILE_SIZE)) ctx.fill() this.tiles.forEach((tile) => { tile.draw(ctx) }) ctx.restore() } update(delta) { } onResize(canvasBounds) { let narrowest = canvasBounds.width let isVertical = true if (canvasBounds.width > canvasBounds.height) { narrowest = canvasBounds.height isVertical = false } Board.TILE_SIZE = Math.min(64, Math.max(40, Math.floor((narrowest - this.boardSize.x * 4) / this.boardSize.x))) const offset = new Point( 0, -(canvasBounds.height / 2) + (10 * (Board.TILE_PADDING + Board.TILE_SIZE)) / 2 ) if (!isVertical) { offset.x = (canvasBounds.width / 2) - (10 * (Board.TILE_PADDING + Board.TILE_SIZE)) / 2 offset.y = 0 } this.position.x = this.boardSize.x * (Board.TILE_PADDING + Board.TILE_SIZE) / 2 this.position.y = this.boardSize.y * (Board.TILE_PADDING + Board.TILE_SIZE) / 2 this.position.offset(offset) } onInputMove(position, isDown) { const offsetPosition = position.addition(this.position) this.tiles.forEach((tile) => { tile.onInputMove(offsetPosition, isDown) }) } onInputDown(position) { const offsetPosition = position.addition(this.position) this.tiles.forEach((tile) => { tile.onInputDown(offsetPosition) }) } onInputUp(position) { const offsetPosition = position.addition(this.position) this.tiles.forEach((tile) => { tile.onInputUp(offsetPosition) }) } selectTile(tile) { if (this.selectedTiles.includes(tile) || this.selectedTiles.length == 2) { tile.isSelected = false return } this.selectedTiles.push(tile) tile.isSelected = true if (this.selectedTiles.length == 2) { this.actionQueue.push(new SwapTileAction(this.selectedTiles[0], this.selectedTiles[1])) this.actionQueue.execute(SwapTileAction, () => this.validToSwap(this.selectedTiles[0], this.selectedTiles[1])).then(() => { this.tiles.forEach((tile) => tile.isSelected = false) this.selectedTiles = [] this.checkForMatches() }, () => { this.selectedTiles.forEach((tile) => tile.isSelected = false) this.selectedTiles.length = 0 }) } } validToSwap(firstTile, otherTile) { if (firstTile.position.x + 1 == otherTile.position.x && firstTile.position.y == otherTile.position.y) { return true } if (firstTile.position.x - 1 == otherTile.position.x && firstTile.position.y == otherTile.position.y) { return true } if (firstTile.position.x == otherTile.position.x && firstTile.position.y + 1 == otherTile.position.y) { return true } if (firstTile.position.x == otherTile.position.x && firstTile.position.y - 1 == otherTile.position.y) { return true } return false } deselectTile(tileToDeselect) { tileToDeselect.isSelected = false this.selectedTiles = this.selectedTiles.filter((tile) => tile != tileToDeselect) } checkForMatches() { const tilesToPop = [] //five in a row horizontal this.tiles.forEach((tile, index, array) => { if (tilesToPop.includes(tile)) { return } const validTiles = this.tiles.filter((checkedTile) => !tilesToPop.includes(checkedTile) && tile.type == checkedTile.type && checkedTile.position.y == tile.position.y && (checkedTile.position.x == tile.position.x + 2 || checkedTile.position.x == tile.position.x + 1 || checkedTile.position.x == tile.position.x - 1 || checkedTile.position.x == tile.position.x - 2)) if (validTiles.length == 4) { tilesToPop.push(tile, ...validTiles) this.actionQueue.push(new MatchTilesAction([tile, ...validTiles])) return } }) //five in a row vertical this.tiles.forEach((tile, index, array) => { if (tilesToPop.includes(tile)) { return } const validTiles = this.tiles.filter((checkedTile) => !tilesToPop.includes(checkedTile) && tile.type == checkedTile.type && checkedTile.position.x == tile.position.x && (checkedTile.position.y == tile.position.y + 2 || checkedTile.position.y == tile.position.y + 1 || checkedTile.position.y == tile.position.y - 1 || checkedTile.position.y == tile.position.y - 2)) if (validTiles.length == 4) { tilesToPop.push(tile, ...validTiles) this.actionQueue.push(new MatchTilesAction([tile, ...validTiles])) return } }) //four in a row horizontal this.tiles.forEach((tile, index, array) => { if (tilesToPop.includes(tile)) { return } const validTiles = this.tiles.filter((checkedTile) => !tilesToPop.includes(checkedTile) && tile.type == checkedTile.type && checkedTile.position.y == tile.position.y && (checkedTile.position.x == tile.position.x + 2 || checkedTile.position.x == tile.position.x + 1 || checkedTile.position.x == tile.position.x - 1)) if (validTiles.length == 3) { tilesToPop.push(tile, ...validTiles) this.actionQueue.push(new MatchTilesAction([tile, ...validTiles])) return } }) //four in a row vertical this.tiles.forEach((tile, index, array) => { if (tilesToPop.includes(tile)) { return } const validTiles = this.tiles.filter((checkedTile) => !tilesToPop.includes(checkedTile) && tile.type == checkedTile.type && checkedTile.position.x == tile.position.x && (checkedTile.position.y == tile.position.y + 2 || checkedTile.position.y == tile.position.y + 1 || checkedTile.position.y == tile.position.y - 1)) if (validTiles.length == 3) { tilesToPop.push(tile, ...validTiles) this.actionQueue.push(new MatchTilesAction([tile, ...validTiles])) return } }) //5 in an L shape //TODO ugh this one is hard //three in a row horizontal this.tiles.forEach((tile, index, array) => { if (tilesToPop.includes(tile)) { return } const validTiles = this.tiles.filter((checkedTile) => !tilesToPop.includes(checkedTile) && tile.type == checkedTile.type && checkedTile.position.y == tile.position.y && (checkedTile.position.x == tile.position.x + 1 || checkedTile.position.x == tile.position.x - 1)) if (validTiles.length == 2) { tilesToPop.push(tile, ...validTiles) this.actionQueue.push(new MatchTilesAction([tile, ...validTiles])) return } }) //three in a row vertical this.tiles.forEach((tile, index, array) => { if (tilesToPop.includes(tile)) { return } const validTiles = this.tiles.filter((checkedTile) => !tilesToPop.includes(checkedTile) && tile.type == checkedTile.type && checkedTile.position.x == tile.position.x && (checkedTile.position.y == tile.position.y + 1 || checkedTile.position.y == tile.position.y - 1)) if (validTiles.length == 2) { tilesToPop.push(tile, ...validTiles) this.actionQueue.push(new MatchTilesAction([tile, ...validTiles])) return } }) tilesToPop.forEach((tile) => { tile.isSelected = true }) if (tilesToPop.length == 0) { return } this.actionQueue.execute(MatchTilesAction).then(() => { this.tiles = this.tiles.filter((tile) => !tilesToPop.includes(tile)) this.spawnAndCascade() }) } spawnAndCascade() { for (let x = 0; x < this.boardSize.x; x++) { const columnTiles = this.tiles.filter((tile) => tile.position.x == x) if (columnTiles.length == this.boardSize.y) { continue } if (columnTiles.length != this.boardSize.y) { this.actionQueue.push(new SpawnTileAction(x, this.boardSize.y - columnTiles.length)) } } this.collapse() this.actionQueue.execute(SpawnTileAction, (x, y) => this.spawnTile(x, y)).then(() => { this.collapse().then(() => { this.checkForMatches() }) }) } collapse() { for (let x = 0; x < this.boardSize.x; x++) { const columnTiles = this.tiles.filter((tile) => tile.position.x == x).sort((tileA, tileB) => tileA.position.y < tileB.position.y) let bottom = this.boardSize.y columnTiles.forEach((tile, index) => { if (tile.position.y < bottom) { bottom -= 1 this.actionQueue.push(new TileFallAction(tile, bottom)) } }) } return this.actionQueue.execute(TileFallAction) } } Board.TILE_PADDING = 4 Board.TILE_SIZE = 64 export default Board