import Phaser from "phaser"; import bg from "./bg.jpg"; import puzzle from "./puzzle.png"; import countdown from "./countdown.png"; import "../../font.css"; const PUZZLE_WH = 1640; const PIECE_WH = 546; const OFFSET_LEFT = 260 + PIECE_WH / 2; const OFFSET_TOP = 390 + PIECE_WH / 2; const surroundingPieces = (z, w, h) => { const x = z % w; const y = Math.floor(z / h); const left = x === 0 ? null : z - 1; const up = y === 0 ? null : z - w; const right = x === w - 1 ? null : z + 1; const down = y === h - 1 ? null : z + w; return [left, up, right, down].filter((i) => i !== null); }; const shuffle = (puzzle, moves, width, height) => { let empty = puzzle[0]; let lastPiece = empty; for (let i = 0; i < moves; i++) { let pieces = surroundingPieces(empty, width, height); pieces = pieces.filter((i) => i !== lastPiece); let pieceIndex = pieces[Math.floor(Math.random() * pieces.length)]; puzzle[empty] = puzzle[pieceIndex]; lastPiece = empty; puzzle[pieceIndex] = 0; empty = pieceIndex; } return puzzle; }; const createShuffledIndexArray = (col) => { const indexArray = new Array(col * col).fill(0).map((_, i) => i); return shuffle(indexArray, 25, col, col); }; class Scene extends Phaser.Scene { constructor() { super("scene"); } preload() { this.load.image("bg", bg); this.load.image("countdown", countdown); this.load.image("totalpuzzle", puzzle); this.load.spritesheet("puzzle", puzzle, { frameWidth: PIECE_WH, frameHeight: PIECE_WH, }); } create() { this.won = false; const bg = this.add.image( this.game.config.width / 2, this.game.config.height / 2, "bg" ); bg.displayWidth = this.game.config.width; bg.displayHeight = this.game.config.height; const countdown = this.add.image(465, 474, "countdown"); countdown.setDepth(1); this.seconds = 300; this.secText = this.add.text(520, 450, this.seconds, { fontSize: 45, fontFamily: "game", color: "#429b47", }); this.secText.setDepth(2); const BOARD_COLS = Math.floor(PUZZLE_WH / PIECE_WH); const BOARD_ROWS = Math.floor(PUZZLE_WH / PIECE_WH); let piecesAmount = BOARD_COLS * BOARD_ROWS; this.time.addEvent({ delay: 1000, loop: true, callback: () => { if (this.seconds === 0) this.game.onLose && this.game.onLose(); if (!this.won && this.seconds > 0) this.seconds--; this.secText.setText(this.seconds); }, }); const shuffledIndexArray = createShuffledIndexArray(BOARD_COLS); let piecesIndex = 0; let piecesGroup = this.add.group(); this.piecesGroup = piecesGroup; for (let i = 0; i < BOARD_ROWS; i++) { for (let j = 0; j < BOARD_COLS; j++) { let piece; if (shuffledIndexArray[piecesIndex]) { piece = piecesGroup.create( OFFSET_LEFT + j * PIECE_WH, OFFSET_TOP + i * PIECE_WH, "puzzle", shuffledIndexArray[piecesIndex] ); } else { //initial position of black piece piece = piecesGroup.create( OFFSET_LEFT + j * PIECE_WH, OFFSET_TOP + i * PIECE_WH, "puzzle", shuffledIndexArray[piecesIndex], false ); piece.black = true; } piece.name = "piece" + i.toString() + "x" + j.toString(); piece.currentIndex = piecesIndex; piece.destIndex = shuffledIndexArray[piecesIndex]; piece.inputEnabled = true; piece.posX = j; piece.posY = i; piece.setInteractive(); piecesIndex++; } } this.input.on( "gameobjectdown", (_, piece) => { var blackPiece = this.canMove(piece); if (blackPiece) { this.movePiece(piece, blackPiece); } }, this ); } canMove(piece) { let foundBlackElem = false; this.piecesGroup.getChildren().forEach(function (element) { if ( (element.posX === piece.posX - 1 && element.posY === piece.posY && element.black) || (element.posX === piece.posX + 1 && element.posY === piece.posY && element.black) || (element.posY === piece.posY - 1 && element.posX === piece.posX && element.black) || (element.posY === piece.posY + 1 && element.posX === piece.posX && element.black) ) { foundBlackElem = element; return; } }); return foundBlackElem; } movePiece(piece, blackPiece) { var tmpPiece = { posX: piece.posX, posY: piece.posY, currentIndex: piece.currentIndex, }; this.tweens.add({ targets: piece, x: OFFSET_LEFT + blackPiece.posX * PIECE_WH, y: OFFSET_TOP + blackPiece.posY * PIECE_WH, ease: "linear", duration: 300, }); piece.posX = blackPiece.posX; piece.posY = blackPiece.posY; piece.currentIndex = blackPiece.currentIndex; piece.name = "piece" + piece.posX.toString() + "x" + piece.posY.toString(); blackPiece.posX = tmpPiece.posX; blackPiece.posY = tmpPiece.posY; blackPiece.currentIndex = tmpPiece.currentIndex; blackPiece.name = "piece" + blackPiece.posX.toString() + "x" + blackPiece.posY.toString(); this.checkIfFinished(); } checkIfFinished() { let isFinished = true; this.piecesGroup.getChildren().forEach((element) => { if (element.currentIndex !== element.destIndex) { isFinished = false; return; } }); if (isFinished) { this.won = true; this.piecesGroup.getChildren().forEach((piece) => { piece.setInteractive(false); piece.visible = false; }); const total = this.add.image( OFFSET_LEFT - PIECE_WH / 2, OFFSET_TOP - PIECE_WH / 2, "totalpuzzle" ); total.displayOriginX = 0; total.displayOriginY = 0; total.displayWidth = PUZZLE_WH; total.displayHeight = PUZZLE_WH; this.game.onWon && this.game.onWon(); } } } export default class Game extends Phaser.Game { constructor({ containerId, onLose, onWon }) { const container = document.getElementById(containerId); const containerWH = container.clientWidth; super({ type: Phaser.CANVAS, width: containerWH, height: containerWH, parent: containerId, scale: { mode: Phaser.Scale.FIT, autoCenter: Phaser.Scale.CENTER_BOTH, parent: containerId, width: 2160, height: 2160, }, scene: Scene, }); Object.assign(this, { onLose, onWon, restart() { this.scene.scenes[0].scene.start("scene"); }, }); } }