import { Camera } from "../../libraries/Camera.js" import { Point } from "../../libraries/spatial/Point.js" import { Theme } from "../../libraries/components/Theme.js" import { Save } from "../../libraries/Save.js" import "../../libraries/RoundRectPolyfill.js" import Tile from "../../libraries/components/matchthree/Tile.js" import Button from "../../libraries/components/matchthree/Button.js" import AsyncTween from "../../libraries/AsyncTween.js" import { Easing } from "../../libraries/Easing.js" export default class MatchThreeState { constructor(view) { this.stateMachine = view.stateMachine this.camera = new Camera() this.camera.scale = new Point(1, 1) this.save = new Save() this.debug = true this.mousePosition = {x: 0, y: 0} this.mouseButton = -1 this.tiles = [] this.buttons = [] this.selectedTiles = [] this.tilesToRemove = [] this.matchPopPromises = [] this.matchCascadePromises = [] } init(scaledCanvas) { this.canvasBounds = scaledCanvas.bounds this.boardSize = { x: 7, y: 7 } this.buttonCount = 6 } enter() { this.registeredEvents = {} this.registeredEvents["resize"] = this.onResize.bind(this) this.registeredEvents["keydown"] = this.onKeyDown.bind(this) this.registeredEvents["keyup"] = this.onKeyUp.bind(this) this.registeredEvents["mousedown"] = this.onMouseDown.bind(this) this.registeredEvents["mousemove"] = this.onMouseMove.bind(this) this.registeredEvents["mouseup"] = this.onMouseUp.bind(this) this.registeredEvents["touchstart"] = this.onTouchStart.bind(this) this.registeredEvents["touchmove"] = this.onTouchMove.bind(this) this.registeredEvents["touchend"] = this.onTouchEnd.bind(this) for (let index in this.registeredEvents) { window.addEventListener(index, this.registeredEvents[index]) } this.camera.targetPosition.x = 0 this.camera.targetPosition.y = 0 this.camera.position.x = 0 this.camera.position.y = 0 this.tiles = [] this.buttons = [] this.selectedTiles = [] for (let y = 0; y < this.boardSize.y; y++) { for (let x = 0; x < this.boardSize.x; x++) { const tileType = Math.floor(Math.random() * Object.values(Theme.Colors.TileColors).length) this.tiles.push(new Tile(this, {x: x, y: y}, tileType)) } } for (let x = 0; x < this.buttonCount; x++) { this.buttons.push(new Button(this, {x: x, y: 0})) } this.tiles.forEach((tile) => { tile.onResize(this.canvasBounds, this.boardSize) }) this.buttons.forEach((button) => { button.setButtonCount(this.buttonCount) button.onResize(this.canvasBounds, this.boardSize) }) this.checkForMatches() } leave(target, leavingCallback) { for (let index in this.registeredEvents) { window.removeEventListener(index, this.registeredEvents[index]) } leavingCallback() } draw(ctx, scaledCanvas) { this.canvasBounds = scaledCanvas.bounds ctx.fillStyle = Theme.Colors.black ctx.fillRect(0, 0, this.canvasBounds.width, this.canvasBounds.height) this.camera.draw(ctx, scaledCanvas, () => { if (this.debug) { ctx.strokeStyle = Theme.Colors.seagreen ctx.beginPath() ctx.moveTo(0, -this.canvasBounds.height) ctx.lineTo(0, this.canvasBounds.height) ctx.stroke() ctx.beginPath() ctx.moveTo(-this.canvasBounds.width, 0) ctx.lineTo(this.canvasBounds.width, 0) ctx.stroke() ctx.beginPath() ctx.moveTo(-this.canvasBounds.width / 4, -this.canvasBounds.height / 4) ctx.lineTo(this.canvasBounds.width / 4, -this.canvasBounds.height / 4) ctx.stroke() ctx.beginPath() ctx.moveTo(-this.canvasBounds.width / 4, this.canvasBounds.height / 4) ctx.lineTo(this.canvasBounds.width / 4, this.canvasBounds.height / 4) ctx.stroke() ctx.strokeStyle = null } this.tiles.forEach((tile) => { tile.draw(ctx) }) this.buttons.forEach((button) => { button.draw(ctx) }) }) } update(delta) { this.camera.update(delta) AsyncTween.update() this.tiles = this.tiles.filter((tile) => !this.tilesToRemove.includes(tile)) this.tilesToRemove = [] } trackSelectedTile(tile) { if(this.selectedTiles.includes(tile) || this.selectedTiles.length == 2) { return } this.selectedTiles.push(tile) if(this.selectedTiles.length == 2) { this.tradeTiles(this.selectedTiles[0], this.selectedTiles[1]) } } tradeTiles(firstTile, otherTile) { if(!this.validToSwap(firstTile, otherTile)) { this.selectedTiles = [] return } let swap = [] swap.push(AsyncTween.create(firstTile.position, otherTile.position, 500, Easing.Elastic.EaseOut).promise) swap.push(AsyncTween.create(otherTile.position, firstTile.position, 500, Easing.Elastic.EaseOut).promise) Promise.all(swap).then(() => { this.selectedTiles = [] this.checkForMatches() }) } 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 } checkForMatches() { const tilesToPop = [] //five in a row horizontal //five in a row vertical //four in a row horizontal //four in a row vertical //5 in an L shape //three in a row horizontal this.tiles.forEach((tile, index, array) => { if(tilesToPop.includes(tile)) { return } const validTiles = this.tiles.filter((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) } }) //three in a row vertical this.tiles.forEach((tile, index, array) => { if(tilesToPop.includes(tile)) { return } const validTiles = this.tiles.filter((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) } }) //push matches into poplist this.popAndCascade(tilesToPop) } popAndCascade(listToPop) { if(listToPop.length == 0) { return } if(this.matchPopPromises.length > 0) { return } let remainingTiles = this.tiles.filter((tile) => !listToPop.includes(tile)) listToPop.forEach((tile) => { this.matchPopPromises.push(AsyncTween.create(tile, {position: {y: (this.canvasBounds.height / 2) + Tile.Size}}, 1000, Easing.Quadratic.EaseIn).promise) }) if(this.matchCascadePromises.length > 0) { return } for(let x = 0; x < this.boardSize.x; x++){ const columnOfTiles = remainingTiles.filter((tile) => tile.position.x == x) let shiftDownAmount = {} for(let y = this.boardSize.y - 1; y > 0; y--) { let spotFilled = columnOfTiles.find((t) => t.position.y == y) if(spotFilled == null) { shiftDownAmount[y] = 1 ?? 0 } } columnOfTiles.forEach((tile) => { let tileShift = 0 Object.keys(shiftDownAmount).forEach((y) => { if(y > tile.position.y) { tileShift++ } }) this.matchCascadePromises.push(AsyncTween.create(tile, {position: {y: tile.position.y + tileShift}}, 200 * tileShift, Easing.Quadratic.EaseIn).promise) }) } Promise.all(this.matchCascadePromises).then(() => { this.matchCascadePromises = [] Promise.all(this.matchPopPromises).then(() => { this.matchPopPromises = [] this.checkForMatches() }) }) } removeTile(tileToRemove) { this.tilesToRemove.push(tileToRemove) } sceneComplete() { this.stateMachine.transitionTo("credits") } getRandomIndex(array, condition) { let item = array[Math.floor(array.length * Math.random())] let timeout = 10 while (!condition(item)) { timeout-- if (timeout <= 0) { throw new Error("unable to get item that meets conditions") } item = array[Math.floor(array.length * Math.random())] } return item } onResize() { this.tiles.forEach((tile) => { tile.onResize(this.canvasBounds, this.boardSize) }) this.buttons.forEach((button) => { button.onResize(this.canvasBounds, this.boardSize) }) } onKeyDown(event) { } onKeyUp(event) { } onMouseDown(event) { this.mouseButton = event.button this.onInputDown() } onMouseMove(event) { let position = this.camera.screenToWorld({x: event.clientX, y: event.clientY}) this.mousePosition.x = position.x this.mousePosition.y = position.y this.onInputMove(this.mouseButton != -1) } onMouseUp(event) { this.mouseButton = -1 this.onInputUp() } onTouchStart(event) { let touch = event.touches[0] let position = this.camera.screenToWorld({x: touch.clientX, y: touch.clientY}) this.mousePosition.x = position.x this.mousePosition.y = position.y this.onInputDown() this.onInputMove(true) } onTouchEnd(event) { let touch = event.touches[0] let position = this.camera.screenToWorld({x: touch.clientX, y: touch.clientY}) this.mousePosition.x = position.x this.mousePosition.y = position.y this.onInputUp() } onTouchMove(event) { let touch = event.touches[0] let position = this.camera.screenToWorld({x: touch.clientX, y: touch.clientY}) this.mousePosition.x = position.x this.mousePosition.y = position.y this.onInputMove(true) } onInputDown() { this.tiles.forEach((tile) => { tile.onInputDown(this.mousePosition) }) this.buttons.forEach((button) => { button.onInputDown(this.mousePosition) }) } onInputUp() { this.tiles.forEach((tile) => { tile.onInputUp(this.mousePosition) }) this.buttons.forEach((button) => { button.onInputUp(this.mousePosition) }) } onInputMove(isDown) { document.body.style.cursor = "default" this.tiles.forEach((tile) => { tile.onInputMove(this.mousePosition, isDown) }) this.buttons.forEach((button) => { button.onInputMove(this.mousePosition, isDown) }) } // digSound1(i, t) { // var n = 5e3; // if (i > n) return null; // return ((Math.pow(i + Math.sin(i * 0.01) * 1000, 0.25) & 200) ? 1 : -1) * Math.pow(t(i, n), 1); // } // digSound2(i, t) { // var n = 5e3; // if (i > n) return null; // return (Math.random() * 2 - 1) * t(i, n); // } // gatherSound(i, t) { // var n=1.6e4; // var c=n/7; // if (i > n) return null; // var q=Math.pow(t(i,n),2.1); // return (i (n - i) / n, // A = new AudioContext(), // m = A.createBuffer(1, 96e3, 48e3), // b = m.getChannelData(0) // for (let i = 96e3; i--;)b[i] = soundFunction(i, t) // let s = A.createBufferSource() // s.buffer = m // s.connect(A.destination) // s.start() // } }