import { Point } from "../../libraries/spatial/Point.js"; import { Rectangle } from "../../libraries/spatial/Rectangle.js"; import { Board } from "../Board.js"; import {Slot} from "../Slot.js"; export class GameState { constructor(fsm) { this.stateMachine = fsm; this.board = null; this.playerData = {current: 1, level: 1}; this.canvasBounds = null; this.showNextButton = false; this.showUndoButton = false; this.movelist = []; this.boundMouseEvent = {}; this.levelData = []; } init() { this.board = new Board(); this.board.init(this); this.boundMouseEvent['mousedown'] = this.mouseDown.bind(this); this.boundMouseEvent['mousemove'] = this.mouseMove.bind(this); for(let key in this.boundMouseEvent) { window.addEventListener(key, this.boundMouseEvent[key]); } //TODO: replace next button //TODO: replace undo button } setLevelData(data) { this.levelData = data; } getLevelData() { return this.levelData; } setPlayerData(data) { this.playerData = data; } getPlayerData() { return this.playerData; } getLevel(levelId) { return this.getLevelData()[levelId]; } draw(ctx, scaledCanvas) { this.canvasBounds = scaledCanvas.bounds; try { ctx.fillStyle = "white"; ctx.strokeStyle = "black"; ctx.font = "48px Luckiest Guy"; ctx.textAlign = "center"; ctx.lineWidth = 4; ctx.fillText(`Level ${this.playerData.current}`, scaledCanvas.center.x, 48); ctx.strokeText(`Level ${this.playerData.current}`, scaledCanvas.center.x, 48); if(this.showNextButton) { this.drawNextButton(ctx, scaledCanvas); } if(this.showUndoButton) { this.drawUndoButton(ctx, scaledCanvas); } this.board.draw(ctx, scaledCanvas); } catch(e) { ctx.setTransform(1, 0, 0, 1, 0, 0); ctx.fillStyle = 'crimson'; ctx.font = "32px Arial"; ctx.fontAlign = "left"; ctx.fillText(`error: ${e.message}`, 4, 32); console.error(e); } if(this.showWinDialog) { this.drawWinDialog(ctx, scaledCanvas); } } update(delta) { this.board.update(delta); } drawNextButton(ctx, scaledCanvas) { let bounds = this.getNextButtonBounds(); ctx.fillStyle = "rgba(255,255,255,0.5)"; ctx.save(); ctx.translate(bounds.x, bounds.y); ctx.beginPath(); ctx.moveTo(bounds.width, bounds.height / 2); ctx.lineTo(bounds.width / 2, bounds.height); ctx.lineTo(bounds.width / 2, 0); ctx.closePath(); ctx.fill(); ctx.fillStyle = "white"; ctx.font = "24px Arial"; ctx.textAlign = "left"; ctx.fillText(`next`, 0, bounds.height/2 + 8); ctx.restore(); } drawUndoButton(ctx, scaledCanvas) { let bounds = this.getUndoButtonBounds(); ctx.fillStyle = "rgba(255,255,255,0.5)"; ctx.save(); ctx.translate(bounds.x, bounds.y); ctx.beginPath(); ctx.rect(0, 0, bounds.width, bounds.height); ctx.closePath(); ctx.fill(); ctx.fillStyle = "white"; ctx.font = "24px Arial"; ctx.textAlign = "center"; let undoText = this.movelist.length < 9 ? `undo (${this.movelist.length})` : `undo`; ctx.fillText(undoText, bounds.width / 2, bounds.height - 20); ctx.restore(); } drawWinDialog(ctx, scaledCanvas) { let bounds = scaledCanvas.bounds; ctx.fillStyle = "rgba(255, 255, 255, 0.5)"; ctx.beginPath(); ctx.rect(bounds.width / 4, bounds.height / 4 + bounds.height / 16, bounds.width / 2, bounds.height / 8); ctx.closePath(); ctx.fill(); ctx.fillStyle = "white"; ctx.strokeStyle = "black"; ctx.font = "48px Luckiest Guy"; ctx.textAlign = "center"; ctx.lineWidth = 4; ctx.fillText(`You Win!`, bounds.center.x, bounds.height / 2 - bounds.height / 8 + 12); ctx.strokeText(`You Win!`, bounds.center.x, bounds.height / 2 - bounds.height / 8 + 12); } mouseDown(event) { let mousePoint = new Point(event.clientX, event.clientY); let nextBounds = this.getNextButtonBounds(); if(this.showNextButton && nextBounds.pointWithin(mousePoint)) { if(this.playerData.level == this.playerData.current) { this.playerData.level++; } this.playerData.current++; localStorage.setItem('ballsort-playerdata', JSON.stringify(this.playerData)); this.board.reset(); this.showNextButton = false; this.showWinDialog = false; } if(this.showUndoButton) { let undoBounds = this.getUndoButtonBounds(); if(undoBounds.pointWithin(mousePoint)) { this.undo(); } } if(!this.showWinDialog) { this.board.mouseDown(event); } } mouseMove(event) { document.body.style.cursor = "default"; let mousePoint = new Point(event.clientX, event.clientY); if(this.showNextButton) { let nextBounds = this.getNextButtonBounds(); if(nextBounds.pointWithin(mousePoint)) { document.body.style.cursor = "pointer"; } } if(this.showUndoButton) { let undoBounds = this.getUndoButtonBounds(); if(undoBounds.pointWithin(mousePoint)) { document.body.style.cursor = "pointer"; } } if(!this.showWinDialog) { this.board.mouseMove(event); } } trackMove(fromSlot, toSlot) { let move = {from: fromSlot, to: toSlot}; this.movelist.push(move); this.showUndoButton = this.movelist.length > 0; } undo() { if(this.board.heldBall) { this.board.placeHeldBallInSlot(this.board.removedFromSlot); return; } let move = this.movelist.pop(); if(move) { let ball = move.to.removeBall(); move.from.addBall(ball); } this.showUndoButton = this.movelist.length > 0; } getNextButtonBounds() { let boardBounds = this.board.getBounds(); let position = { x: this.canvasBounds.center.x, y: boardBounds.y + boardBounds.height + 20, } if(this.canvasBounds.height < 660) { position.x = this.canvasBounds.width - 100; position.y = Slot.height + (Slot.heightPadding / 2) + boardBounds.y - 25; } return new Rectangle(position.x, position.y, 100, 50); } getUndoButtonBounds() { let boardBounds = this.board.getBounds(); let position = { x: this.canvasBounds.center.x, y: boardBounds.y + boardBounds.height + 20, } if(this.canvasBounds.height < 660) { position.x = this.canvasBounds.width - 100; position.y = Slot.height + (Slot.heightPadding / 2) + boardBounds.y - 25; } return new Rectangle(position.x, position.y, 100, 50); } win() { setTimeout(() => { this.showWinDialog = true; this.showNextButton = true; this.showUndoButton = false; this.movelist = []; }, 400); } enter() { this.init(this.stateMachine.getState("game")); } leave() { this.board.destroy(); for(let key in this.boundMouseEvent) { window.removeEventListener(key, this.boundMouseEvent[key]); } } }