import { LEVEL_CONFIG } from './config.js'; export class GameScene extends Phaser.Scene { constructor() { super({ key: 'GameScene' }); this.gridConfig = { allPadding: 50, leftPadding: 700, rows: 8, cols: 8, jewelWidth: 100, jewelHeight: 100, }; // Stats this.numberOfJewels = 4; this.startRows = 2; this.level = 12; this.matchesNeeded = 8; this.score = 0; this.ally = 'goblin'; this.spritePlus = 0; this.gridColor = 0x000000; // Status Indication this.isDropping = false; this.isSwapping = false; this.isDestroying = false; this.isMovingUp = false; this.isPlayingVideo = false; this.isPlayingAudio = false; this.gameStatus = true; this.rowOne = false; // Add selectedJewel property this.selectedJewel = null; this.swapWithJewel = null; // Timer properties this.moveTimer = 0; this.moveInterval = 12000; this.timerText = null; this.countdownTimer = false; } preload() { // Jewels this.load.spritesheet('jewels', 'assets/jewels.png', { frameWidth: 100, frameHeight: 100 }); // Fonts this.load.font('cruiser', 'assets/NEUROPOL.ttf'); this.load.font('code', 'assets/CodePredators-Regular.otf'); // Music this.load.audio('clear', 'assets/clear.mp3'); this.load.audio('level-up', 'assets/level-up.mp3'); this.load.audio('level-complete', 'assets/level-complete.mp3'); this.load.audio('switch', 'assets/switch.mp3'); this.load.audio('game-over', 'assets/game-over-boom.mp3'); this.load.audio('countdown', 'assets/countdown.mp3'); this.load.audio('alarm', 'assets/alarm.mp3'); // Ally Assets const allys = ['goblin', 'surfer', 'bear', 'wizard']; allys.forEach((ally) => { this.load.video(`${ally}-resting`, `assets/${ally}-resting.mp4`); this.load.video(`${ally}-excited`, `assets/${ally}-excited.mp4`); this.load.video(`${ally}-pleased`, `assets/${ally}-pleased.mp4`); this.load.video(`${ally}-match`, `assets/${ally}-match.mp4`); this.load.video(`${ally}-background`, `assets/${ally}-background.mp4`); this.load.audio(`${ally}-a1`, `assets/${ally}-a1.mp3`); this.load.audio(`${ally}-a2`, `assets/${ally}-a2.mp3`); this.load.audio(`${ally}-a3`, `assets/${ally}-a3.mp3`); this.load.audio(`${ally}-b`, `assets/${ally}-b.mp3`); this.load.audio(`${ally}-c`, `assets/${ally}-c.mp3`); this.load.audio(`${ally}-intro`, `assets/${ally}-intro.mp3`); this.load.audio(`${ally}-outro`, `assets/${ally}-outro.mp3`); this.load.audio(`${ally}-music`, `assets/${ally}-music.mp3`); }); } create() { // Set Start Level const newLevel = LEVEL_CONFIG[this.level]; this.numberOfJewels = newLevel.numberOfJewels; this.matchesNeeded = newLevel.matchesNeeded; this.moveInterval = newLevel.moveInterval; this.ally = newLevel.ally; this.spritePlus = newLevel.spritePlus; this.gridColor = newLevel.gridColor; // Background Video this.bgVideo = this.add.video(0, 0, `${this.ally}-background`); this.bgVideo.setOrigin(0); console.log("width",this.scale.width); this.bgVideo.scaleX = this.scale.width / 848; this.bgVideo.scaleY = this.scale.height / 480; this.bgVideo.play(true); // Create the Game Grid this.makeGrid(this.gridColor); this.physics.world.setBounds(this.grid.getBounds().x - 50, this.grid.getBounds().y - 50, this.grid.getBounds().width + 100, this.grid.getBounds().height + 100); this.jewels = this.physics.add.group({ collideWorldBounds: true, }); this.physics.add.collider(this.jewels, this.jewels); this.createStart(); // Background Music this.bgMusic = this.sound.add(`${this.ally}-music`, { volume: 0.2 }); this.bgMusic.loop = true; this.bgMusic.play(); // Create Ally Video this.allyVideo = this.add.video(350, 610, `${this.ally}-resting`).setOrigin(0.5); this.allyVideo.play(true); this.allyVideo.postFX.addGlow(); this.time.delayedCall(500, () => { this.sound.play(`${this.ally}-intro`); }); // Create Text Background this.add.rectangle(10, 40, 650, 270, 0x000000, .5).setOrigin(0); // Create the score text this.scoreText = this.add.text(20, 50, 'Score: 0', { fontFamily: 'cruiser, arial', fontSize: '36px', fill: '#ffffff', padding: { left: 10, right: 10, top: 5, bottom: 5 } }).setOrigin(0); // Create the Level text this.LevelText = this.add.text(20, 116, `Level: ${this.level}`, { fontFamily: 'cruiser, arial', fontSize: '36px', fill: '#ffffff', padding: { left: 10, right: 10, top: 5, bottom: 5 } }).setOrigin(0); // Create the timer text this.timerText = this.add.text(20, 182, 'Next Row:', { fontFamily: 'cruiser, arial', fontSize: '36px', fill: '#ffffff', padding: { left: 10, right: 10, top: 5, bottom: 5 } }).setOrigin(0); // Create the Matches Needed text this.matchesText = this.add.text(20, 248, `Remaining Matches: ${this.matchesNeeded}`, { fontFamily: 'cruiser, arial', fontSize: '36px', fill: '#ffffff', padding: { left: 10, right: 10, top: 5, bottom: 5 } }).setOrigin(0); } update(time, delta) { // Update the move timer this.moveTimer += delta; // Check if it's time to move all jewels up if ( this.moveTimer >= this.moveInterval && this.isDropping === false && this.isSwapping === false && this.isDestroying === false && this.gameStatus === true ) { this.moveAllJewelsUp(); this.moveTimer = 0; // Reset the timer } if (this.moveInterval - this.moveTimer <= 3000) { this.startCountdownTimer(); } // Update the timer display const timeRemaining = (this.moveInterval - this.moveTimer) / 1000; const displayTime = timeRemaining.toFixed(2); if (this.timerText) { this.timerText.setText(`Next Row: ${displayTime}`); } } startCountdownTimer() { if (this.countdownTimer === true || this.gameStatus === false) { return; } this.countdownTimer = true; this.sound.play('countdown'); this.time.delayedCall(6000, () => { this.countdownTimer = false; }); } makeGrid(gridColor = 0x000000) { this.grid = this.add.rectangle( this.gridConfig.leftPadding + this.gridConfig.allPadding, 0 + this.gridConfig.allPadding, this.gridConfig.cols * this.gridConfig.jewelWidth, this.gridConfig.rows*this.gridConfig.jewelHeight, gridColor, .5 ).setOrigin(0); this.grid.setInteractive(); this.grid.on('pointerdown', (pointer) => { this.handleGridClick(pointer.x, pointer.y); }); } createJewel(type, col, row) { // Create a jewel sprite from the spritesheet using the type as frame index const jewel = this.physics.add.sprite( col * this.gridConfig.jewelWidth + this.gridConfig.leftPadding, row * this.gridConfig.jewelHeight, 'jewels', type + this.spritePlus ); jewel.setOrigin(0.5); jewel.setDisplaySize(this.gridConfig.jewelWidth, this.gridConfig.jewelHeight); jewel.jewelType = type; this.jewels.add(jewel); // Add click event to the jewel jewel.setInteractive(); jewel.on('pointerdown', () => { this.handleJewelClick(jewel.x, jewel.y); }); return jewel; } createStart() { // Create jewels at the bottom of the grid let createWait = 0; const startRow = this.gridConfig.rows - this.startRows; for (let row = this.startRows; row > 0; row--) { for (let col = 1; col <= this.gridConfig.cols; col++) { let type = null; let match = false; do { type = Phaser.Math.Between(0, this.numberOfJewels - 1) match = false; if (this.getJewelAtPosition(col-1, startRow + row) === type && this.getJewelAtPosition(col-2, startRow + row) === type) { match = true; } if (this.getJewelAtPosition(col, startRow + row + 1) === type && this.getJewelAtPosition(col, startRow + row + 2) === type) { match = true; } } while (match === true); // Create the jewel at the correct position this.createJewel(type, col, startRow + row); } } } getJewelAtPosition(col, row) { // Convert grid coordinates to world coordinates const x = col * this.gridConfig.jewelWidth + this.gridConfig.leftPadding; const y = row * this.gridConfig.jewelHeight; // Find the jewel at that position let jewelAtPosition = null; // Iterate through all jewels to find one near the specified position this.jewels.children.iterate((jewel) => { if (jewel && Math.abs(jewel.x - x) < this.gridConfig.jewelWidth / 2 && Math.abs(jewel.y - y) < this.gridConfig.jewelHeight / 2) { jewelAtPosition = jewel; return false; // Stop iteration } }); // Return the jewel type or null if not found return jewelAtPosition ? jewelAtPosition.jewelType : null; } // Handle clicks on the grid handleGridClick(x, y) { if (!this.selectedJewel) { return; } const selectedX = this.selectedJewel.x; const selectedY = this.selectedJewel.y; // Calculate which grid tile was clicked const clickCol = Math.floor((x+50) / this.gridConfig.jewelWidth); const clickRow = Math.floor((y+50) / this.gridConfig.jewelHeight); // Calculate selected jewel's grid position const selectedCol = Math.floor(selectedX / this.gridConfig.jewelWidth); const selectedRow = Math.floor(selectedY / this.gridConfig.jewelHeight); // Check if click is adjacent (up, down, left, right) const isAdjacent = (clickCol === selectedCol && Math.abs(clickRow - selectedRow) === 1) || // Up or down (clickRow === selectedRow && Math.abs(clickCol - selectedCol) === 1); // Left or right if (isAdjacent) { this.tweens.add({ targets: this.selectedJewel, scale: 1, duration: 200 }); this.moveJewel(clickCol * this.gridConfig.jewelWidth, clickRow * this.gridConfig.jewelHeight); } } // Modified swap function to disable clicks during execution moveJewel(x ,y) { if (this.isMovingUp === true) { this.time.delayedCall(1000, ()=> { this.moveJewel(x, y); }) return; } // Set swapping flag to prevent new clicks from being processed this.isSwapping = true; this.sound.play('switch'); // Disable all jewel interactivity temporarily this.jewels.children.iterate((jewel) => { if (jewel) { jewel.disableInteractive(); } }); // Perform the swap animation here - currently empty const fromX = this.selectedJewel.x; const fromY = this.selectedJewel.y; const toX = x; const toY = y; this.tweens.add({ targets: this.selectedJewel, x: toX, y: toY, duration: 300, onComplete: () => { this.dropJewels(); this.time.delayedCall(300, () => { this.checkMatches(); }) } }); this.selectedJewel = null; this.swapWithJewel = null; // After swap completes, re-enable interactivity and reset flag this.time.delayedCall(300, () => { // Adjust delay as needed for animation duration this.jewels.children.iterate((jewel) => { if (jewel) { jewel.setInteractive(); } }); this.isSwapping = false; }); } // New function to handle jewel clicks handleJewelClick(x, y) { // Find which jewel was clicked const clickedJewel = this.getJewelAtWorldPosition(x, y); //const isAdjacentButEmpty = this.isAdjacentButEmtpy(this.selectedJewel, x, y); if (!clickedJewel || this.isSwapping) { return; } // If no jewel is currently selected if (!this.selectedJewel) { this.selectJewel(clickedJewel); return; } // If clicking on the already selected jewel, deselect it if (clickedJewel === this.selectedJewel) { this.deselectJewel(); return; } // Check if clicked jewel is adjacent to selected jewel const isAdjacent = this.isAdjacent(this.selectedJewel, clickedJewel); if (isAdjacent) { // Mark for swap this.selectJewelForSwap(clickedJewel); } else { // Deselect current and select new jewel this.deselectJewel(); this.selectJewel(clickedJewel); } } // Helper function to get jewel at world position getJewelAtWorldPosition(x, y) { let closestJewel = null; let minDistance = Infinity; this.jewels.children.iterate((jewel) => { if (jewel) { const distance = Phaser.Math.Distance.Between(jewel.x, jewel.y, x, y); if (distance < minDistance && distance < 50) { // 50 is half of jewel width/height minDistance = distance; closestJewel = jewel; } } }); return closestJewel; } // Helper function to check if two jewels are adjacent (horizontally or vertically) isAdjacent(jewel1, jewel2) { const col1 = Math.floor((jewel1.x - this.gridConfig.leftPadding) / this.gridConfig.jewelWidth); const row1 = Math.floor(jewel1.y / this.gridConfig.jewelHeight); const col2 = Math.floor((jewel2.x - this.gridConfig.leftPadding) / this.gridConfig.jewelWidth); const row2 = Math.floor(jewel2.y / this.gridConfig.jewelHeight); // Check if jewels are adjacent (same row and adjacent columns, or same column and adjacent rows) return (row1 === row2 && Math.abs(col1 - col2) === 1) || (col1 === col2 && Math.abs(row1 - row2) === 1); } // Helper function to select a jewel for swap selectJewelForSwap(jewel) { this.swapWithJewel = jewel; this.tweens.add({ targets: this.selectedJewel, scale: 1, duration: 200 }); this.swapJewel(); } // Helper function to select a jewel for swap selectJewel(jewel) { this.selectedJewel = jewel; this.tweens.add({ targets: jewel, scale: 1.2, duration: 200 }); } // Helper function to deselect a jewel deselectJewel() { if (this.selectedJewel) { this.tweens.add({ targets: this.selectedJewel, scale: 1, duration: 200 }); this.selectedJewel = null; } } // Modified swap function to disable clicks during execution swapJewel() { if (this.isMovingUp === true) { this.time.delayedCall(500, ()=> { this.swapJewel(); }) return; } // Set swapping flag to prevent new clicks from being processed this.isSwapping = true; this.sound.play('switch'); // Disable all jewel interactivity temporarily this.jewels.children.iterate((jewel) => { if (jewel) { jewel.disableInteractive(); } }); // Perform the swap animation here - currently empty const fromX = this.selectedJewel.x; const fromY = this.selectedJewel.y; const toX = this.swapWithJewel.x; const toY = this.swapWithJewel.y; this.tweens.add({ targets: this.selectedJewel, x: toX, y: toY, duration: 300 }); this.tweens.add({ targets: this.swapWithJewel, x: fromX, y: fromY, duration: 300, onComplete: () => { this.checkMatches(); } }); this.selectedJewel = null; this.swapWithJewel = null; // After swap completes, re-enable interactivity and reset flag this.time.delayedCall(300, () => { // Adjust delay as needed for animation duration this.jewels.children.iterate((jewel) => { if (jewel) { jewel.setInteractive(); } }); this.isSwapping = false; }); } reduceMatches(amount) { this.matchesNeeded -= amount; if (this.matchesNeeded <= 0) { this.levelUp(); } this.matchesText.setText(`Matches Needed: ${this.matchesNeeded}`); } // Function to check for matches and destroy them checkMatches() { this.checkWarning(); const matchedJewels = new Set(); // Check horizontal matches for (let row = 1; row <= this.gridConfig.rows; row++) { let count = 1; let currentType = this.getJewelAtPosition(1, row); for (let col = 2; col <= this.gridConfig.cols; col++) { const jewelType = this.getJewelAtPosition(col, row); if (jewelType === currentType && jewelType !== null) { count++; } else { if (count >= 3) { this.reduceMatches(1); // Add all jewels in this match to the matched set for (let i = col - count; i < col; i++) { const key = `${i},${row}`; matchedJewels.add(key); } } count = 1; currentType = jewelType; } } // Check for match at the end of row if (count >= 3) { this.reduceMatches(1); for (let i = this.gridConfig.cols - count + 1; i <= this.gridConfig.cols; i++) { const key = `${i},${row}`; matchedJewels.add(key); } } } // Check vertical matches for (let col = 1; col <= this.gridConfig.cols; col++) { let count = 1; let currentType = this.getJewelAtPosition(col, 0); for (let row = 1; row <= this.gridConfig.rows; row++) { const jewelType = this.getJewelAtPosition(col, row); if (jewelType === currentType && jewelType !== null) { count++; } else { if (count >= 3) { this.reduceMatches(1); // Add all jewels in this match to the matched set for (let i = row - count; i < row; i++) { const key = `${col},${i}`; matchedJewels.add(key); } } count = 1; currentType = jewelType; } } // Check for match at the end of column if (count >= 3) { this.reduceMatches(1); for (let i = this.gridConfig.rows - count + 1; i <= this.gridConfig.rows; i++) { const key = `${col},${i}`; matchedJewels.add(key); } } } // If we found matches, destroy them and play video if (matchedJewels.size > 0) { this.playVideo(matchedJewels.size); this.playAudio(matchedJewels.size); const scoreMatches = matchedJewels.size / 3; const scoreAdd = Math.ceil(scoreMatches ** 2) * 10; this.score += scoreAdd; this.scoreText.setText(`Score: ${this.score}`); // ADD SCORE this.destroyMatchedJewels(matchedJewels); return true; } return false; } playVideo(amount) { if (this.isPlayingVideo === true) { return; } this.isPlayingVideo = true; let video = null; if (amount >= 5) { video = `${this.ally}-excited`; } else if (amount >= 4) { video = `${this.ally}-pleased`; } else { video = `${this.ally}-match`; } const showVideo = this.add.video(350, 610, video).setOrigin(0.5).setDepth(100); showVideo.play(false); this.time.delayedCall(5000, () => { this.isPlayingVideo = false; showVideo.destroy(); }); } playAudio(amount) { if (this.isPlayingAudio === true) { return; } this.isPlayingAudio = true; let audio = null; if (amount >= 5) { audio = `${this.ally}-c`; } else if (amount >= 4) { audio = `${this.ally}-b`; } else { const rand = Phaser.Math.Between(1, 3); audio = `${this.ally}-a${rand}`; } this.sound.play(audio); this.time.delayedCall(2000, () => { this.isPlayingAudio = false; }); } // Function to destroy matched jewels destroyMatchedJewels(matchedJewels) { if (this.isDestroying) { return; } this.isDestroying = true; // Create an array of jewels to destroy const jewelsToDestroy = []; this.jewels.children.iterate((jewel) => { if (jewel) { const col = Math.floor((jewel.x - this.gridConfig.leftPadding) / this.gridConfig.jewelWidth); const row = Math.floor(jewel.y / this.gridConfig.jewelHeight); const key = `${col},${row}`; if (matchedJewels.has(key)) { jewelsToDestroy.push(jewel); } } }); this.sound.play('clear'); // Animate destruction jewelsToDestroy.forEach((jewel) => { this.tweens.add({ targets: jewel, scaleX: 0, scaleY: 0, alpha: 0, duration: 200, onComplete: () => { jewel.destroy(); this.time.delayedCall(100, () => { this.isDestroying = false; this.dropJewels(); }); } }); }); } dropJewels() { if (this.isDropping) { return; } this.isDropping = true; // Create a grid representation to track jewel positions const grid = []; for (let row = 1; row <= this.gridConfig.rows; row++) { grid[row] = []; for (let col = 1; col <= this.gridConfig.cols; col++) { grid[row][col] = null; } } // Populate the grid with jewel positions this.jewels.children.iterate((jewel) => { if (jewel) { const col = Math.floor((jewel.x - this.gridConfig.leftPadding) / this.gridConfig.jewelWidth); const row = Math.floor(jewel.y / this.gridConfig.jewelHeight); // Ensure we're within grid bounds if (col >= 1 && col <= this.gridConfig.cols && row >= 1 && row <= this.gridConfig.rows) { grid[row][col] = jewel; } } }); // Find jewels that need to drop and their target positions const jewelsToDrop = []; for (let col = 1; col <= this.gridConfig.cols; col++) { let emptySpaces = 0; // Process from bottom to top for (let row = this.gridConfig.rows; row >= 1; row--) { if (grid[row][col] === null) { emptySpaces++; } else if (emptySpaces > 0) { // This jewel needs to drop const jewel = grid[row][col]; const targetRow = row + emptySpaces; jewelsToDrop.push({ jewel: jewel, fromRow: row, toRow: targetRow }); // Update grid reference grid[row][col] = null; grid[targetRow][col] = jewel; } } } // If no jewels need to drop, we're done if (jewelsToDrop.length === 0) { this.isDropping = false; return; } // Animate the dropping let droppedCount = 0; jewelsToDrop.forEach((dropInfo) => { const { jewel, fromRow, toRow } = dropInfo; // Calculate target y position const targetY = toRow * this.gridConfig.jewelHeight; // Animate the jewel dropping this.tweens.add({ targets: jewel, y: targetY, duration: 300, ease: 'Linear', onComplete: () => { droppedCount++; // When all jewels have dropped, check for new matches if (droppedCount === jewelsToDrop.length) { this.time.delayedCall(400, () => { this.isDropping = false; // Check for new matches after dropping this.checkMatches(); }); } } }); }); } // Function to move all jewels up one row moveAllJewelsUp() { this.isMovingUp = true; // Check if any jewel is at row 1 (topmost) that would go to row 0 let gameOver = false; this.jewels.children.iterate((jewel) => { if (jewel) { const col = Math.floor((jewel.x - this.gridConfig.leftPadding) / this.gridConfig.jewelWidth); const row = Math.floor(jewel.y / this.gridConfig.jewelHeight); // If jewel is at row 1 and would move up to row 0, game over if (row === 1 && col >= 1 && col <= this.gridConfig.cols) { gameOver = true; } } }); // If game over, end the game if (gameOver) { this.gameOver(); return; } // Move all jewels up one row this.jewels.children.iterate((jewel) => { if (jewel) { const col = Math.floor((jewel.x - this.gridConfig.leftPadding) / this.gridConfig.jewelWidth); const row = Math.floor(jewel.y / this.gridConfig.jewelHeight); // Only move jewels that are not at the top row if (row > 1 && col >= 1 && col <= this.gridConfig.cols) { const targetRow = row - 1; const targetY = targetRow * this.gridConfig.jewelHeight; // Animate the jewel moving up this.tweens.add({ targets: jewel, y: targetY, duration: 300, ease: 'Linear' }); } } }); // Create new bottom row after moving all jewels up this.time.delayedCall(300, () => { this.isMovingUp = false; this.createBottomRow(); this.checkWarning(); }); } // Function to create a new row of jewels at the bottom createBottomRow() { const newRow = 8; let jewelDelay = 0; for (let col = 1; col <= this.gridConfig.cols; col++) { let type = null; let match = false; do { type = Phaser.Math.Between(0, this.numberOfJewels - 1); match = false; // Check horizontal matches in the new row if (col >= 3) { // Check if this would create a horizontal match of 3 or more const leftType = this.getJewelAtPosition(col-1, newRow); const leftLeftType = this.getJewelAtPosition(col-2, newRow); if (leftType === type && leftLeftType === type) { match = true; } } // Check vertical matches in the columns above if (newRow <= this.gridConfig.rows) { const aboveType = this.getJewelAtPosition(col, newRow - 1); if (aboveType === type) { // Check if there's a match of 3 or more by looking at additional positions let count = 1; // Look up to see how many matching jewels we have in the column for (let row = newRow - 2; row < this.gridConfig.rows; row++) { const jewelType = this.getJewelAtPosition(col, row); if (jewelType === type) { count++; } else { break; } } // If we have 3 or more in a vertical line, it's a match if (count >= 3) { match = true; } } } // Also check for matches with jewels above the new row (if they exist) if (newRow < this.gridConfig.rows && col <= this.gridConfig.cols) { const jewelAbove = this.getJewelAtPosition(col, newRow - 1); if (jewelAbove !== null) { // Check horizontal match with jewels in the same row let count = 0; // Look left to see how many matching jewels we have in that row for (let c = col - 1; c >= 1; c--) { const jewelType = this.getJewelAtPosition(c, newRow); if (jewelType === type) { count++; } else { break; } } // Look right to see how many matching jewels we have in that row for (let c = col + 1; c <= this.gridConfig.cols; c++) { const jewelType = this.getJewelAtPosition(c, newRow); if (jewelType === type) { count++; } else { break; } } // If we have at least 2 matching jewels in the row and one more above, // or any other match scenario that creates a 3-in-a-row if (count >= 2 && jewelAbove === type) { match = true; } } } } while (match === true); // Create the jewel at the correct position this.createJewel(type, col, newRow); } } refreshJewelSprites() { // Iterate through all jewels and update their sprite frames this.jewels.children.iterate((jewel) => { if (jewel && jewel.jewelType !== undefined) { // Update the jewel's frame to reflect the new spritePlus value jewel.setFrame(jewel.jewelType + this.spritePlus); } }); } refreshScene(originalAlly) { // Stop the current music this.bgMusic.stop(); // Play the new ally's music this.bgMusic = this.sound.add(`${this.ally}-music`, { volume: 0.2 }); this.bgMusic.loop = true; this.bgMusic.play(); this.time.delayedCall(4000, () => { this.sound.play(`${this.ally}-intro`); }); // Refresh the ally video with the new ally if (this.allyVideo) { console.log('update-vid ',this.ally); // Destroy the existing video this.allyVideo.destroy(); // Create a new video with the updated ally this.allyVideo = this.add.video(350, 610, `${this.ally}-resting`).setOrigin(0.5); this.allyVideo.play(true); this.allyVideo.postFX.addGlow(); } // Refresh the background video with the new ally if (this.bgVideo) { // Destroy the existing background video this.bgVideo.destroy(); // Create a new background video with the updated ally this.bgVideo = this.add.video(0, 0, `${this.ally}-background`).setOrigin(0); this.bgVideo.scaleX = this.scale.width / 848; this.bgVideo.scaleY = this.scale.height / 480; this.bgVideo.play(true).setDepth(-1); } } checkWarning() { // Check if there are any jewels in row 1 (topmost row) let hasJewelsInRow1 = false; this.time.delayedCall(500, () => { this.jewels.children.iterate((jewel) => { if (jewel) { const col = Math.floor((jewel.x - this.gridConfig.leftPadding) / this.gridConfig.jewelWidth); const row = Math.floor(jewel.y / this.gridConfig.jewelHeight); // If jewel is in row 1, set the flag to true if (row === 1 && col >= 1 && col <= this.gridConfig.cols) { hasJewelsInRow1 = true; return false; // Stop iteration once we find one } } }); if (this.rowOne === true && hasJewelsInRow1 === false) { this.rowOne = false; this.grid.setFillStyle(this.gridColor).setAlpha(0.5); this.alarm.stop(); this.alarmFlash.remove(); this.alarmFlash = null; } else if (this.rowOne === false && hasJewelsInRow1) { console.log('in row one'); this.rowOne = true; this.alarm = this.sound.add('alarm', { loop: true, volume: 1 }); this.alarm.play(); this.alarmFlash = this.tweens.add({ targets: this.grid, fillColor: 0xFF0000, duration: 3000, alpha: .7, ease: 'Linear', yoyo: true, repeat: -1 }); } }); } levelUp() { this.level ++; const newLevel = LEVEL_CONFIG[this.level]; this.numberOfJewels = newLevel.numberOfJewels; this.matchesNeeded = newLevel.matchesNeeded; this.moveInterval = newLevel.moveInterval; this.gridColor = newLevel.gridColor; if (newLevel.challenge !== undefined) { this.clearAndResetBoard(); } this.LevelText.setText(`Level: ${this.level}`); this.sound.play('level-up'); this.sound.play('level-complete'); this.grid.setFillStyle(newLevel.gridColor).setAlpha(0.5); if (this.ally !== newLevel.ally) { this.sound.play(`${this.ally}-outro`); this.time.delayedCall(1000, () => { const originalAlly = this.ally; this.ally = newLevel.ally; this.refreshScene(originalAlly); this.spritePlus = newLevel.spritePlus; this.refreshJewelSprites(); }); } // Create the New Level text const newLevelText = this.add.text(1150, 250, `Level ${this.level}`, { fontFamily: 'code, arial', fontSize: '100px', fill: '#ea00ffff', padding: { left: 10, right: 10, top: 5, bottom: 5 } }).setOrigin(0.5); // Add cool eye-catching effect this.tweens.add({ targets: newLevelText, scale: { from: 0.5, to: 1.5 }, alpha: { from: 0, to: 1 }, duration: 1000, ease: 'Back.easeOut', yoyo: true }); // Fade out and destroy after 500ms this.time.delayedCall(1200, () => { this.tweens.add({ targets: newLevelText, angle: 360, scale: 0, alpha: 0, duration: 500, onComplete: () => { newLevelText.destroy(); } }); }); } clearAndResetBoard() { // Destroy all existing jewels this.jewels.children.iterate((jewel) => { if (jewel) { jewel.destroy(); } }); // Clear the jewels group this.jewels.clear(true, true); // Bring in 2 fresh rows this.startRows = 2; this.createStart(); } // Game over function gameOver() { console.log('Game Over!'); this.gameStatus = false; this.sound.play('game-over'); this.rowOne = false; this.grid.setFillStyle(this.gridColor).setAlpha(0.5); this.alarm.stop(); this.alarmFlash.remove(); this.alarmFlash = null; // Make all jewels bounce off screen this.jewels.children.iterate((jewel) => { if (jewel) { // Enable physics for the jewel if not already enabled if (!jewel.body) { this.physics.add.existing(jewel); } // Remove world bounds collision jewel.body.setCollideWorldBounds(false); // Set random velocity for bouncing effect const angle = Phaser.Math.Between(0, 360); const speed = Phaser.Math.Between(100, 300); jewel.body.setVelocity( Math.cos(angle) * speed, Math.sin(angle) * speed ); // Add some rotation for visual effect jewel.body.angularVelocity = Phaser.Math.Between(-200, 200); } }); // Create the New Level text const gameOverText = this.add.text(1150, 250, `Game Over`, { fontFamily: 'code, arial', fontSize: '100px', fill: '#ea00ffff', padding: { left: 10, right: 10, top: 5, bottom: 5 } }).setOrigin(0.5); // Add cool eye-catching effect this.tweens.add({ targets: gameOverText, scale: { from: 0.5, to: 1.5 }, alpha: { from: 0, to: 1 }, duration: 1000, ease: 'Back.easeOut', }); // Fade out and destroy after 2000ms this.time.delayedCall(10200, () => { this.tweens.add({ targets: gameOverText, angle: 360, scale: 0, alpha: 0, duration: 500, onComplete: () => { gameOverText.destroy(); this.time.delayedCall(3000, () => { // Fade out camera this.tweens.add({ targets: this.cameras.main, opacity: 0, duration: 1500, ease: 'Linear', onComplete: () => { this.scene.restart(); } }); }); } }); }); } }